Browse Source

ImportSelector.getCandidateFilter() for transitive filtering of classes

Closes gh-24175
pull/24481/head
Juergen Hoeller 6 years ago
parent
commit
d93303c008
  1. 130
      spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
  2. 3
      spring-context/src/main/java/org/springframework/context/annotation/DeferredImportSelector.java
  3. 21
      spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java
  4. 17
      spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java

130
spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -34,6 +34,7 @@ import java.util.List; @@ -34,6 +34,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -110,6 +111,9 @@ class ConfigurationClassParser { @@ -110,6 +111,9 @@ class ConfigurationClassParser {
private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory();
private static final Predicate<String> DEFAULT_CANDIDATE_FILTER = className ->
(className.startsWith("java.lang.annotation.") || className.startsWith("org.springframework.stereotype."));
private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR =
(o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getImportSelector(), o2.getImportSelector());
@ -191,15 +195,15 @@ class ConfigurationClassParser { @@ -191,15 +195,15 @@ class ConfigurationClassParser {
protected final void parse(@Nullable String className, String beanName) throws IOException {
Assert.notNull(className, "No bean class name for configuration class bean definition");
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
processConfigurationClass(new ConfigurationClass(reader, beanName));
processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_CANDIDATE_FILTER);
}
protected final void parse(Class<?> clazz, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(clazz, beanName));
processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_CANDIDATE_FILTER);
}
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_CANDIDATE_FILTER);
}
/**
@ -217,7 +221,7 @@ class ConfigurationClassParser { @@ -217,7 +221,7 @@ class ConfigurationClassParser {
}
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
@ -240,9 +244,9 @@ class ConfigurationClassParser { @@ -240,9 +244,9 @@ class ConfigurationClassParser {
}
// Recursively process the configuration class and its superclass hierarchy.
SourceClass sourceClass = asSourceClass(configClass);
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
@ -258,12 +262,13 @@ class ConfigurationClassParser { @@ -258,12 +262,13 @@ class ConfigurationClassParser {
* @return the superclass, or {@code null} if none found or previously processed
*/
@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);
processMemberClasses(configClass, sourceClass, filter);
}
// Process any @PropertySource annotations
@ -302,7 +307,7 @@ class ConfigurationClassParser { @@ -302,7 +307,7 @@ class ConfigurationClassParser {
}
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// Process any @ImportResource annotations
AnnotationAttributes importResource =
@ -343,7 +348,9 @@ class ConfigurationClassParser { @@ -343,7 +348,9 @@ class ConfigurationClassParser {
/**
* Register member (nested) classes that happen to be configuration classes themselves.
*/
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass,
Predicate<String> filter) throws IOException {
Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
if (!memberClasses.isEmpty()) {
List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
@ -361,7 +368,7 @@ class ConfigurationClassParser { @@ -361,7 +368,7 @@ class ConfigurationClassParser {
else {
this.importStack.push(configClass);
try {
processConfigurationClass(candidate.asConfigClass(configClass));
processConfigurationClass(candidate.asConfigClass(configClass), filter);
}
finally {
this.importStack.pop();
@ -543,7 +550,8 @@ class ConfigurationClassParser { @@ -543,7 +550,8 @@ class ConfigurationClassParser {
}
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
Collection<SourceClass> importCandidates, Predicate<String> candidateFilter,
boolean checkForCircularImports) {
if (importCandidates.isEmpty()) {
return;
@ -561,13 +569,17 @@ class ConfigurationClassParser { @@ -561,13 +569,17 @@ class ConfigurationClassParser {
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
Predicate<String> selectorFilter = selector.getCandidateFilter();
if (selectorFilter != null) {
candidateFilter = candidateFilter.or(selectorFilter);
}
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, candidateFilter);
processImports(configClass, currentSourceClass, importSourceClasses, candidateFilter, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
@ -584,7 +596,7 @@ class ConfigurationClassParser { @@ -584,7 +596,7 @@ class ConfigurationClassParser {
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
processConfigurationClass(candidate.asConfigClass(configClass), candidateFilter);
}
}
}
@ -624,19 +636,19 @@ class ConfigurationClassParser { @@ -624,19 +636,19 @@ class ConfigurationClassParser {
/**
* Factory method to obtain a {@link SourceClass} from a {@link ConfigurationClass}.
*/
private SourceClass asSourceClass(ConfigurationClass configurationClass) throws IOException {
private SourceClass asSourceClass(ConfigurationClass configurationClass, Predicate<String> filter) throws IOException {
AnnotationMetadata metadata = configurationClass.getMetadata();
if (metadata instanceof StandardAnnotationMetadata) {
return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass());
return asSourceClass(((StandardAnnotationMetadata) metadata).getIntrospectedClass(), filter);
}
return asSourceClass(metadata.getClassName());
return asSourceClass(metadata.getClassName(), filter);
}
/**
* Factory method to obtain a {@link SourceClass} from a {@link Class}.
*/
SourceClass asSourceClass(@Nullable Class<?> classType) throws IOException {
if (classType == null || classType.getName().startsWith("java.lang.annotation.")) {
SourceClass asSourceClass(@Nullable Class<?> classType, Predicate<String> filter) throws IOException {
if (classType == null || filter.test(classType.getName())) {
return this.objectSourceClass;
}
try {
@ -649,17 +661,17 @@ class ConfigurationClassParser { @@ -649,17 +661,17 @@ class ConfigurationClassParser {
}
catch (Throwable ex) {
// Enforce ASM via class name resolution
return asSourceClass(classType.getName());
return asSourceClass(classType.getName(), filter);
}
}
/**
* Factory method to obtain {@link SourceClass SourceClasss} from class names.
*/
private Collection<SourceClass> asSourceClasses(String... classNames) throws IOException {
private Collection<SourceClass> asSourceClasses(String[] classNames, Predicate<String> filter) throws IOException {
List<SourceClass> annotatedClasses = new ArrayList<>(classNames.length);
for (String className : classNames) {
annotatedClasses.add(asSourceClass(className));
annotatedClasses.add(asSourceClass(className, filter));
}
return annotatedClasses;
}
@ -667,23 +679,20 @@ class ConfigurationClassParser { @@ -667,23 +679,20 @@ class ConfigurationClassParser {
/**
* Factory method to obtain a {@link SourceClass} from a class name.
*/
SourceClass asSourceClass(@Nullable String className) throws IOException {
if (className == null || className.startsWith("java.lang.annotation.")) {
SourceClass asSourceClass(@Nullable String className, Predicate<String> filter) throws IOException {
if (className == null || filter.test(className)) {
return this.objectSourceClass;
}
if (className.startsWith("java")) {
// Never use ASM for core java types
try {
return new SourceClass(ClassUtils.forName(className,
this.resourceLoader.getClassLoader()));
return new SourceClass(ClassUtils.forName(className, this.resourceLoader.getClassLoader()));
}
catch (ClassNotFoundException ex) {
throw new NestedIOException(
"Failed to load class [" + className + "]", ex);
throw new NestedIOException("Failed to load class [" + className + "]", ex);
}
}
return new SourceClass(
this.metadataReaderFactory.getMetadataReader(className));
return new SourceClass(this.metadataReaderFactory.getMetadataReader(className));
}
@ -748,8 +757,7 @@ class ConfigurationClassParser { @@ -748,8 +757,7 @@ class ConfigurationClassParser {
* @param importSelector the selector to handle
*/
public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(
configClass, importSelector);
DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
if (this.deferredImportSelectors == null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
handler.register(holder);
@ -775,7 +783,6 @@ class ConfigurationClassParser { @@ -775,7 +783,6 @@ class ConfigurationClassParser {
this.deferredImportSelectors = new ArrayList<>();
}
}
}
@ -786,8 +793,7 @@ class ConfigurationClassParser { @@ -786,8 +793,7 @@ class ConfigurationClassParser {
private final Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
public void register(DeferredImportSelectorHolder deferredImport) {
Class<? extends Group> group = deferredImport.getImportSelector()
.getImportGroup();
Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
(group != null ? group : deferredImport),
key -> new DeferredImportSelectorGrouping(createGroup(group)));
@ -798,12 +804,13 @@ class ConfigurationClassParser { @@ -798,12 +804,13 @@ class ConfigurationClassParser {
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
Predicate<String> candidateFilter = grouping.getCandidateFilter();
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(
entry.getMetadata());
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
processImports(configurationClass, asSourceClass(configurationClass),
asSourceClasses(entry.getImportClassName()), false);
processImports(configurationClass, asSourceClass(configurationClass, candidateFilter),
Collections.singleton(asSourceClass(entry.getImportClassName(), candidateFilter)),
candidateFilter, false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
@ -818,15 +825,12 @@ class ConfigurationClassParser { @@ -818,15 +825,12 @@ class ConfigurationClassParser {
}
private Group createGroup(@Nullable Class<? extends Group> type) {
Class<? extends Group> effectiveType = (type != null ? type
: DefaultDeferredImportSelectorGroup.class);
Group group = ParserStrategyUtils.instantiateClass(effectiveType, Group.class,
Class<? extends Group> effectiveType = (type != null ? type : DefaultDeferredImportSelectorGroup.class);
return ParserStrategyUtils.instantiateClass(effectiveType, Group.class,
ConfigurationClassParser.this.environment,
ConfigurationClassParser.this.resourceLoader,
ConfigurationClassParser.this.registry);
return group;
}
}
@ -861,6 +865,10 @@ class ConfigurationClassParser { @@ -861,6 +865,10 @@ class ConfigurationClassParser {
this.group = group;
}
public Group getGroup() {
return this.group;
}
public void add(DeferredImportSelectorHolder deferredImport) {
this.deferredImports.add(deferredImport);
}
@ -876,6 +884,17 @@ class ConfigurationClassParser { @@ -876,6 +884,17 @@ class ConfigurationClassParser {
}
return this.group.selectImports();
}
public Predicate<String> getCandidateFilter() {
Predicate<String> mergedFilter = DEFAULT_CANDIDATE_FILTER;
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
Predicate<String> selectorFilter = deferredImport.getImportSelector().getCandidateFilter();
if (selectorFilter != null) {
mergedFilter = mergedFilter.or(selectorFilter);
}
}
return mergedFilter;
}
}
@ -957,7 +976,7 @@ class ConfigurationClassParser { @@ -957,7 +976,7 @@ class ConfigurationClassParser {
Class<?>[] declaredClasses = sourceClass.getDeclaredClasses();
List<SourceClass> members = new ArrayList<>(declaredClasses.length);
for (Class<?> declaredClass : declaredClasses) {
members.add(asSourceClass(declaredClass));
members.add(asSourceClass(declaredClass, DEFAULT_CANDIDATE_FILTER));
}
return members;
}
@ -974,7 +993,7 @@ class ConfigurationClassParser { @@ -974,7 +993,7 @@ class ConfigurationClassParser {
List<SourceClass> members = new ArrayList<>(memberClassNames.length);
for (String memberClassName : memberClassNames) {
try {
members.add(asSourceClass(memberClassName));
members.add(asSourceClass(memberClassName, DEFAULT_CANDIDATE_FILTER));
}
catch (IOException ex) {
// Let's skip it if it's not resolvable - we're just looking for candidates
@ -989,9 +1008,10 @@ class ConfigurationClassParser { @@ -989,9 +1008,10 @@ class ConfigurationClassParser {
public SourceClass getSuperClass() throws IOException {
if (this.source instanceof Class) {
return asSourceClass(((Class<?>) this.source).getSuperclass());
return asSourceClass(((Class<?>) this.source).getSuperclass(), DEFAULT_CANDIDATE_FILTER);
}
return asSourceClass(((MetadataReader) this.source).getClassMetadata().getSuperClassName());
return asSourceClass(
((MetadataReader) this.source).getClassMetadata().getSuperClassName(), DEFAULT_CANDIDATE_FILTER);
}
public Set<SourceClass> getInterfaces() throws IOException {
@ -999,12 +1019,12 @@ class ConfigurationClassParser { @@ -999,12 +1019,12 @@ class ConfigurationClassParser {
if (this.source instanceof Class) {
Class<?> sourceClass = (Class<?>) this.source;
for (Class<?> ifcClass : sourceClass.getInterfaces()) {
result.add(asSourceClass(ifcClass));
result.add(asSourceClass(ifcClass, DEFAULT_CANDIDATE_FILTER));
}
}
else {
for (String className : this.metadata.getInterfaceNames()) {
result.add(asSourceClass(className));
result.add(asSourceClass(className, DEFAULT_CANDIDATE_FILTER));
}
}
return result;
@ -1018,7 +1038,7 @@ class ConfigurationClassParser { @@ -1018,7 +1038,7 @@ class ConfigurationClassParser {
Class<?> annType = ann.annotationType();
if (!annType.getName().startsWith("java")) {
try {
result.add(asSourceClass(annType));
result.add(asSourceClass(annType, DEFAULT_CANDIDATE_FILTER));
}
catch (Throwable ex) {
// An annotation not present on the classpath is being ignored
@ -1060,7 +1080,7 @@ class ConfigurationClassParser { @@ -1060,7 +1080,7 @@ class ConfigurationClassParser {
if (this.source instanceof Class) {
try {
Class<?> clazz = ClassUtils.forName(className, ((Class<?>) this.source).getClassLoader());
return asSourceClass(clazz);
return asSourceClass(clazz, DEFAULT_CANDIDATE_FILTER);
}
catch (ClassNotFoundException ex) {
// Ignore -> fall back to ASM next, except for core java types.
@ -1070,7 +1090,7 @@ class ConfigurationClassParser { @@ -1070,7 +1090,7 @@ class ConfigurationClassParser {
return new SourceClass(metadataReaderFactory.getMetadataReader(className));
}
}
return asSourceClass(className);
return asSourceClass(className, DEFAULT_CANDIDATE_FILTER);
}
@Override

3
spring-context/src/main/java/org/springframework/context/annotation/DeferredImportSelector.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -51,6 +51,7 @@ public interface DeferredImportSelector extends ImportSelector { @@ -51,6 +51,7 @@ public interface DeferredImportSelector extends ImportSelector {
/**
* Interface used to group results from different import selectors.
* @since 5.0
*/
interface Group {

21
spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,7 +16,10 @@ @@ -16,7 +16,10 @@
package org.springframework.context.annotation;
import java.util.function.Predicate;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented by types that determine which @{@link Configuration}
@ -48,6 +51,7 @@ import org.springframework.core.type.AnnotationMetadata; @@ -48,6 +51,7 @@ import org.springframework.core.type.AnnotationMetadata;
* (see {@link DeferredImportSelector} for details).
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
* @see DeferredImportSelector
* @see Import
@ -59,7 +63,22 @@ public interface ImportSelector { @@ -59,7 +63,22 @@ public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
* @return the class names, or an empty array if none
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
/**
* Return a predicate for filtering candidate classes, to be transitively
* applied to all candidate classes found through this selector's imports.
* <p>If this predicate returns {@code true} for a given class name,
* said class will not be considered as a configuration class,
* bypassing class loading as well as metadata introspection.
* @return the filter predicate, or {@code null} if none
* @since 5.2.4
*/
@Nullable
default Predicate<String> getCandidateFilter() {
return null;
}
}

17
spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -29,6 +29,7 @@ import java.util.LinkedList; @@ -29,6 +29,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.junit.jupiter.api.BeforeEach;
@ -101,13 +102,17 @@ public class ImportSelectorTests { @@ -101,13 +102,17 @@ public class ImportSelectorTests {
}
@Test
public void correctMetaDataOnIndirectImports() {
new AnnotationConfigApplicationContext(IndirectConfig.class);
public void correctMetadataOnIndirectImports() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(IndirectConfig.class);
String indirectImport = IndirectImport.class.getName();
assertThat(importFrom.get(ImportSelector1.class)).isEqualTo(indirectImport);
assertThat(importFrom.get(ImportSelector2.class)).isEqualTo(indirectImport);
assertThat(importFrom.get(DeferredImportSelector1.class)).isEqualTo(indirectImport);
assertThat(importFrom.get(DeferredImportSelector2.class)).isEqualTo(indirectImport);
assertThat(context.containsBean("a")).isFalse(); // since ImportedSelector1 got filtered
assertThat(context.containsBean("b")).isTrue();
assertThat(context.containsBean("c")).isTrue();
assertThat(context.containsBean("d")).isTrue();
}
@Test
@ -361,6 +366,12 @@ public class ImportSelectorTests { @@ -361,6 +366,12 @@ public class ImportSelectorTests {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {IndirectImport.class.getName()};
}
@Override
@Nullable
public Predicate<String> getCandidateFilter() {
return className -> className.endsWith("ImportedSelector1");
}
}

Loading…
Cancel
Save