diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 5be7fe892ba5..7b406d6c9115 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -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.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 { private static final PropertySourceFactory DEFAULT_PROPERTY_SOURCE_FACTORY = new DefaultPropertySourceFactory(); + private static final Predicate DEFAULT_CANDIDATE_FILTER = className -> + (className.startsWith("java.lang.annotation.") || className.startsWith("org.springframework.stereotype.")); + private static final Comparator DEFERRED_IMPORT_COMPARATOR = (o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getImportSelector(), o2.getImportSelector()); @@ -191,15 +195,15 @@ else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).h 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 @@ public Set getConfigurationClasses() { } - protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { + protected void processConfigurationClass(ConfigurationClass configClass, Predicate filter) throws IOException { if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } @@ -240,9 +244,9 @@ protected void processConfigurationClass(ConfigurationClass configClass) throws } // 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 @@ protected void processConfigurationClass(ConfigurationClass configClass) throws * @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 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 @@ protected final SourceClass doProcessConfigurationClass(ConfigurationClass confi } // 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 @@ protected final SourceClass doProcessConfigurationClass(ConfigurationClass confi /** * 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 filter) throws IOException { + Collection memberClasses = sourceClass.getMemberClasses(); if (!memberClasses.isEmpty()) { List candidates = new ArrayList<>(memberClasses.size()); @@ -361,7 +368,7 @@ private void processMemberClasses(ConfigurationClass configClass, SourceClass so else { this.importStack.push(configClass); try { - processConfigurationClass(candidate.asConfigClass(configClass)); + processConfigurationClass(candidate.asConfigClass(configClass), filter); } finally { this.importStack.pop(); @@ -543,7 +550,8 @@ private void collectImports(SourceClass sourceClass, Set imports, S } private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, - Collection importCandidates, boolean checkForCircularImports) { + Collection importCandidates, Predicate candidateFilter, + boolean checkForCircularImports) { if (importCandidates.isEmpty()) { return; @@ -561,13 +569,17 @@ private void processImports(ConfigurationClass configClass, SourceClass currentS Class candidateClass = candidate.loadClass(); ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry); + Predicate 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 importSourceClasses = asSourceClasses(importClassNames); - processImports(configClass, currentSourceClass, importSourceClasses, false); + Collection importSourceClasses = asSourceClasses(importClassNames, candidateFilter); + processImports(configClass, currentSourceClass, importSourceClasses, candidateFilter, false); } } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { @@ -584,7 +596,7 @@ else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // 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 @@ ImportRegistry getImportRegistry() { /** * Factory method to obtain a {@link SourceClass} from a {@link ConfigurationClass}. */ - private SourceClass asSourceClass(ConfigurationClass configurationClass) throws IOException { + private SourceClass asSourceClass(ConfigurationClass configurationClass, Predicate 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 filter) throws IOException { + if (classType == null || filter.test(classType.getName())) { return this.objectSourceClass; } try { @@ -649,17 +661,17 @@ SourceClass asSourceClass(@Nullable Class classType) throws IOException { } 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 asSourceClasses(String... classNames) throws IOException { + private Collection asSourceClasses(String[] classNames, Predicate filter) throws IOException { List annotatedClasses = new ArrayList<>(classNames.length); for (String className : classNames) { - annotatedClasses.add(asSourceClass(className)); + annotatedClasses.add(asSourceClass(className, filter)); } return annotatedClasses; } @@ -667,23 +679,20 @@ private Collection asSourceClasses(String... classNames) throws IOE /** * 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 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 @@ private class DeferredImportSelectorHandler { * @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 @@ public void process() { this.deferredImportSelectors = new ArrayList<>(); } } - } @@ -786,8 +793,7 @@ private class DeferredImportSelectorGroupingHandler { private final Map configurationClasses = new HashMap<>(); public void register(DeferredImportSelectorHolder deferredImport) { - Class group = deferredImport.getImportSelector() - .getImportGroup(); + Class group = deferredImport.getImportSelector().getImportGroup(); DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent( (group != null ? group : deferredImport), key -> new DeferredImportSelectorGrouping(createGroup(group))); @@ -798,12 +804,13 @@ public void register(DeferredImportSelectorHolder deferredImport) { public void processGroupImports() { for (DeferredImportSelectorGrouping grouping : this.groupings.values()) { + Predicate 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 @@ public void processGroupImports() { } private Group createGroup(@Nullable Class type) { - Class effectiveType = (type != null ? type - : DefaultDeferredImportSelectorGroup.class); - Group group = ParserStrategyUtils.instantiateClass(effectiveType, Group.class, + Class 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 @@ private static class DeferredImportSelectorGrouping { this.group = group; } + public Group getGroup() { + return this.group; + } + public void add(DeferredImportSelectorHolder deferredImport) { this.deferredImports.add(deferredImport); } @@ -876,6 +884,17 @@ public Iterable getImports() { } return this.group.selectImports(); } + + public Predicate getCandidateFilter() { + Predicate mergedFilter = DEFAULT_CANDIDATE_FILTER; + for (DeferredImportSelectorHolder deferredImport : this.deferredImports) { + Predicate selectorFilter = deferredImport.getImportSelector().getCandidateFilter(); + if (selectorFilter != null) { + mergedFilter = mergedFilter.or(selectorFilter); + } + } + return mergedFilter; + } } @@ -957,7 +976,7 @@ public Collection getMemberClasses() throws IOException { Class[] declaredClasses = sourceClass.getDeclaredClasses(); List 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 @@ public Collection getMemberClasses() throws IOException { List 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 @@ public Collection getMemberClasses() throws IOException { 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 getInterfaces() throws IOException { @@ -999,12 +1019,12 @@ public Set getInterfaces() throws IOException { 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 @@ public Set getAnnotations() { 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 @@ private SourceClass getRelated(String className) throws IOException { 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 @@ private SourceClass getRelated(String className) throws IOException { return new SourceClass(metadataReaderFactory.getMetadataReader(className)); } } - return asSourceClass(className); + return asSourceClass(className, DEFAULT_CANDIDATE_FILTER); } @Override diff --git a/spring-context/src/main/java/org/springframework/context/annotation/DeferredImportSelector.java b/spring-context/src/main/java/org/springframework/context/annotation/DeferredImportSelector.java index 6bca9156781c..dedb068b6497 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/DeferredImportSelector.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/DeferredImportSelector.java @@ -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 @@ default Class getImportGroup() { /** * Interface used to group results from different import selectors. + * @since 5.0 */ interface Group { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java b/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java index 94edc6374e48..df4fd0436ae4 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java @@ -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 @@ 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 @@ * (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 { /** * 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. + *

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 getCandidateFilter() { + return null; + } + } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java index 08af14feedbd..b13682f786fa 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ImportSelectorTests.java @@ -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.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 void invokeAwareMethodsInImportSelector() { } @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 static class IndirectImportSelector implements ImportSelector { public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[] {IndirectImport.class.getName()}; } + + @Override + @Nullable + public Predicate getCandidateFilter() { + return className -> className.endsWith("ImportedSelector1"); + } }