ImportSelector.getCandidateFilter() for transitive filtering of classes
Closes gh-24175
This commit is contained in:
@@ -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;
|
||||
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<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 {
|
||||
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 {
|
||||
}
|
||||
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
* @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 {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
/**
|
||||
* 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 {
|
||||
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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
// 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 {
|
||||
/**
|
||||
* 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 {
|
||||
}
|
||||
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 {
|
||||
/**
|
||||
* 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 {
|
||||
* @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 {
|
||||
this.deferredImportSelectors = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public Group getGroup() {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
public void add(DeferredImportSelectorHolder deferredImport) {
|
||||
this.deferredImports.add(deferredImport);
|
||||
}
|
||||
@@ -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 {
|
||||
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 {
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
return new SourceClass(metadataReaderFactory.getMetadataReader(className));
|
||||
}
|
||||
}
|
||||
return asSourceClass(className);
|
||||
return asSourceClass(className, DEFAULT_CANDIDATE_FILTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
* Interface used to group results from different import selectors.
|
||||
* @since 5.0
|
||||
*/
|
||||
interface Group {
|
||||
|
||||
|
||||
@@ -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 @@ 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 {
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user