All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.context.annotation.ConfigurationClassParser Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2002-2024 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.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.annotation;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
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;

import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.parsing.Location;
import org.springframework.beans.factory.parsing.Problem;
import org.springframework.beans.factory.parsing.ProblemReporter;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase;
import org.springframework.context.annotation.DeferredImportSelector.Group;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PropertySourceDescriptor;
import org.springframework.core.io.support.PropertySourceProcessor;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

/**
 * Parses a {@link Configuration} class definition, populating a collection of
 * {@link ConfigurationClass} objects (parsing a single Configuration class may result in
 * any number of ConfigurationClass objects because one Configuration class may import
 * another using the {@link Import} annotation).
 *
 * 

This class helps separate the concern of parsing the structure of a Configuration class * from the concern of registering BeanDefinition objects based on the content of that model * (except {@code @ComponentScan} annotations which need to be registered immediately). * *

This ASM-based implementation avoids reflection and eager class loading in order to * interoperate effectively with lazy class loading in a Spring ApplicationContext. * * @author Chris Beams * @author Juergen Hoeller * @author Phillip Webb * @author Sam Brannen * @author Stephane Nicoll * @since 3.0 * @see ConfigurationClassBeanDefinitionReader */ class ConfigurationClassParser { private static final Predicate DEFAULT_EXCLUSION_FILTER = className -> (className.startsWith("java.lang.annotation.") || className.startsWith("org.springframework.stereotype.")); private static final Predicate REGISTER_BEAN_CONDITION_FILTER = condition -> (condition instanceof ConfigurationCondition configurationCondition && ConfigurationPhase.REGISTER_BEAN.equals(configurationCondition.getConfigurationPhase())); private static final Comparator DEFERRED_IMPORT_COMPARATOR = (o1, o2) -> AnnotationAwareOrderComparator.INSTANCE.compare(o1.getImportSelector(), o2.getImportSelector()); private final Log logger = LogFactory.getLog(getClass()); private final MetadataReaderFactory metadataReaderFactory; private final ProblemReporter problemReporter; private final Environment environment; private final ResourceLoader resourceLoader; @Nullable private final PropertySourceRegistry propertySourceRegistry; private final BeanDefinitionRegistry registry; private final ComponentScanAnnotationParser componentScanParser; private final ConditionEvaluator conditionEvaluator; private final Map configurationClasses = new LinkedHashMap<>(); private final MultiValueMap knownSuperclasses = new LinkedMultiValueMap<>(); private final ImportStack importStack = new ImportStack(); private final DeferredImportSelectorHandler deferredImportSelectorHandler = new DeferredImportSelectorHandler(); private final SourceClass objectSourceClass = new SourceClass(Object.class); /** * Create a new {@link ConfigurationClassParser} instance that will be used * to populate the set of configuration classes. */ public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter, Environment environment, ResourceLoader resourceLoader, BeanNameGenerator componentScanBeanNameGenerator, BeanDefinitionRegistry registry) { this.metadataReaderFactory = metadataReaderFactory; this.problemReporter = problemReporter; this.environment = environment; this.resourceLoader = resourceLoader; this.propertySourceRegistry = (this.environment instanceof ConfigurableEnvironment ce ? new PropertySourceRegistry(new PropertySourceProcessor(ce, this.resourceLoader)) : null); this.registry = registry; this.componentScanParser = new ComponentScanAnnotationParser( environment, resourceLoader, componentScanBeanNameGenerator, registry); this.conditionEvaluator = new ConditionEvaluator(registry, environment, resourceLoader); } public void parse(Set configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition annotatedBeanDef) { parse(annotatedBeanDef, holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition abstractBeanDef && abstractBeanDef.hasBeanClass()) { parse(abstractBeanDef.getBeanClass(), holder.getBeanName()); } else { parse(bd.getBeanClassName(), holder.getBeanName()); } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); } } this.deferredImportSelectorHandler.process(); } private void parse(AnnotatedBeanDefinition beanDef, String beanName) { processConfigurationClass( new ConfigurationClass(beanDef.getMetadata(), beanName, (beanDef instanceof ScannedGenericBeanDefinition)), DEFAULT_EXCLUSION_FILTER); } private void parse(Class clazz, String beanName) { processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER); } 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), DEFAULT_EXCLUSION_FILTER); } /** * Validate each {@link ConfigurationClass} object. * @see ConfigurationClass#validate */ void validate() { for (ConfigurationClass configClass : this.configurationClasses.keySet()) { configClass.validate(this.problemReporter); } } Set getConfigurationClasses() { return this.configurationClasses.keySet(); } List getPropertySourceDescriptors() { return (this.propertySourceRegistry != null ? this.propertySourceRegistry.getDescriptors() : Collections.emptyList()); } ImportRegistry getImportRegistry() { return this.importStack; } protected void processConfigurationClass(ConfigurationClass configClass, Predicate filter) { if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; } ConfigurationClass existingClass = this.configurationClasses.get(configClass); if (existingClass != null) { if (configClass.isImported()) { if (existingClass.isImported()) { existingClass.mergeImportedBy(configClass); } // Otherwise ignore new imported config class; existing non-imported class overrides it. return; } else if (configClass.isScanned()) { String beanName = configClass.getBeanName(); if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) { this.registry.removeBeanDefinition(beanName); } // An implicitly scanned bean definition should not override an explicit import. return; } else { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. this.configurationClasses.remove(configClass); removeKnownSuperclass(configClass.getMetadata().getClassName(), false); } } // Recursively process the configuration class and its superclass hierarchy. SourceClass sourceClass = null; try { sourceClass = asSourceClass(configClass, filter); do { sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter); } while (sourceClass != null); } catch (IOException ex) { throw new BeanDefinitionStoreException( "I/O failure while processing configuration class [" + sourceClass + "]", ex); } this.configurationClasses.put(configClass, configClass); } /** * Apply processing and build a complete {@link ConfigurationClass} by reading the * annotations, members and methods from the source class. This method can be called * multiple times as relevant sources are discovered. * @param configClass the configuration class being build * @param sourceClass a source class * @return the superclass, or {@code null} if none found or previously processed */ @Nullable 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, filter); } // Process any @PropertySource annotations for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), org.springframework.context.annotation.PropertySource.class, PropertySources.class, true)) { if (this.propertySourceRegistry != null) { this.propertySourceRegistry.processPropertySource(propertySource); } else { logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); } } // Search for locally declared @ComponentScan annotations first. Set componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScan.class, ComponentScans.class, MergedAnnotation::isDirectlyPresent); // Fall back to searching for @ComponentScan meta-annotations (which indirectly // includes locally declared composed annotations). if (componentScans.isEmpty()) { componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScan.class, ComponentScans.class, MergedAnnotation::isMetaPresent); } if (!componentScans.isEmpty()) { List registerBeanConditions = collectRegisterBeanConditions(configClass); if (!registerBeanConditions.isEmpty()) { throw new ApplicationContextException( "Component scan for configuration class [%s] could not be used with conditions in REGISTER_BEAN phase: %s" .formatted(configClass.getMetadata().getClassName(), registerBeanConditions)); } for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately Set scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); } } } } // Process any @Import annotations processImports(configClass, sourceClass, getImports(sourceClass), filter, true); // Process any @ImportResource annotations AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } } // Process individual @Bean methods Set beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { if (methodMetadata.isAnnotated("kotlin.jvm.JvmStatic") && !methodMetadata.isStatic()) { continue; } configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces processInterfaces(configClass, sourceClass); // Process superclass, if any if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass != null && !superclass.startsWith("java")) { boolean superclassKnown = this.knownSuperclasses.containsKey(superclass); this.knownSuperclasses.add(superclass, configClass); if (!superclassKnown) { // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } } // No superclass -> processing is complete return null; } /** * Register member (nested) classes that happen to be configuration classes themselves. */ private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass, Predicate filter) throws IOException { Collection memberClasses = sourceClass.getMemberClasses(); if (!memberClasses.isEmpty()) { List candidates = new ArrayList<>(memberClasses.size()); for (SourceClass memberClass : memberClasses) { if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) && !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) { candidates.add(memberClass); } } OrderComparator.sort(candidates); for (SourceClass candidate : candidates) { if (this.importStack.contains(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { processConfigurationClass(candidate.asConfigClass(configClass), filter); } finally { this.importStack.pop(); } } } } } /** * Register default methods on interfaces implemented by the configuration class. */ private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { for (SourceClass ifc : sourceClass.getInterfaces()) { Set beanMethods = retrieveBeanMethodMetadata(ifc); for (MethodMetadata methodMetadata : beanMethods) { if (!methodMetadata.isAbstract()) { // A default method or other concrete method on a Java 8+ interface... configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } } processInterfaces(configClass, ifc); } } /** * Retrieve the metadata for all @Bean methods. */ private Set retrieveBeanMethodMetadata(SourceClass sourceClass) { AnnotationMetadata original = sourceClass.getMetadata(); Set beanMethods = original.getAnnotatedMethods(Bean.class.getName()); if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) { // Try reading the class file via ASM for deterministic declaration order... // Unfortunately, the JVM's standard reflection returns methods in arbitrary // order, even between different runs of the same application on the same JVM. try { AnnotationMetadata asm = this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata(); Set asmMethods = asm.getAnnotatedMethods(Bean.class.getName()); if (asmMethods.size() >= beanMethods.size()) { Set candidateMethods = new LinkedHashSet<>(beanMethods); Set selectedMethods = CollectionUtils.newLinkedHashSet(asmMethods.size()); for (MethodMetadata asmMethod : asmMethods) { for (Iterator it = candidateMethods.iterator(); it.hasNext();) { MethodMetadata beanMethod = it.next(); if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) { selectedMethods.add(beanMethod); it.remove(); break; } } } if (selectedMethods.size() == beanMethods.size()) { // All reflection-detected methods found in ASM method set -> proceed beanMethods = selectedMethods; } } } catch (IOException ex) { logger.debug("Failed to read class file via ASM for determining @Bean method order", ex); // No worries, let's continue with the reflection metadata we started with... } } return beanMethods; } /** * Remove known superclasses for the given removed class, potentially replacing * the superclass exposure on a different config class with the same superclass. */ private void removeKnownSuperclass(String removedClass, boolean replace) { String replacedSuperclass = null; ConfigurationClass replacingClass = null; Iterator>> it = this.knownSuperclasses.entrySet().iterator(); while (it.hasNext()) { Map.Entry> entry = it.next(); if (entry.getValue().removeIf(configClass -> configClass.getMetadata().getClassName().equals(removedClass))) { if (entry.getValue().isEmpty()) { it.remove(); } else if (replace && replacingClass == null) { replacedSuperclass = entry.getKey(); replacingClass = entry.getValue().get(0); } } } if (replacingClass != null) { try { SourceClass sourceClass = asSourceClass(replacingClass, DEFAULT_EXCLUSION_FILTER).getSuperClass(); while (!sourceClass.getMetadata().getClassName().equals(replacedSuperclass) && sourceClass.getMetadata().getSuperClassName() != null) { sourceClass = sourceClass.getSuperClass(); } do { sourceClass = doProcessConfigurationClass(replacingClass, sourceClass, DEFAULT_EXCLUSION_FILTER); } while (sourceClass != null); } catch (IOException ex) { throw new BeanDefinitionStoreException( "I/O failure while removing configuration class [" + removedClass + "]", ex); } } } /** * Returns {@code @Import} classes, considering all meta-annotations. */ private Set getImports(SourceClass sourceClass) throws IOException { Set imports = new LinkedHashSet<>(); collectImports(sourceClass, imports, new HashSet<>()); return imports; } /** * Recursively collect all declared {@code @Import} values. Unlike most * meta-annotations it is valid to have several {@code @Import}s declared with * different values; the usual process of returning values from the first * meta-annotation on a class is not sufficient. *

For example, it is common for a {@code @Configuration} class to declare direct * {@code @Import}s in addition to meta-imports originating from an {@code @Enable} * annotation. * @param sourceClass the class to search * @param imports the imports collected so far * @param visited used to track visited classes to prevent infinite recursion * @throws IOException if there is any problem reading metadata from the named class */ private void collectImports(SourceClass sourceClass, Set imports, Set visited) throws IOException { if (visited.add(sourceClass)) { for (SourceClass annotation : sourceClass.getAnnotations()) { String annName = annotation.getMetadata().getClassName(); if (!annName.equals(Import.class.getName())) { collectImports(annotation, imports, visited); } } imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value")); } } private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection importCandidates, Predicate filter, boolean checkForCircularImports) { if (importCandidates.isEmpty()) { return; } if (checkForCircularImports && isChainedImportOnStack(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { for (SourceClass candidate : importCandidates) { if (candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class candidateClass = candidate.loadClass(); ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry); Predicate selectorFilter = selector.getExclusionFilter(); if (selectorFilter != null) { filter = filter.or(selectorFilter); } if (selector instanceof DeferredImportSelector deferredImportSelector) { this.deferredImportSelectorHandler.handle(configClass, deferredImportSelector); } else { String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection importSourceClasses = asSourceClasses(importClassNames, filter); processImports(configClass, currentSourceClass, importSourceClasses, filter, false); } } else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // Candidate class is an ImportBeanDefinitionRegistrar -> // delegate to it to register additional bean definitions Class candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry); configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata()); } else { // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -> // process it as an @Configuration class this.importStack.registerImport( currentSourceClass.getMetadata(), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass), filter); } } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]: " + ex.getMessage(), ex); } finally { this.importStack.pop(); } } } private boolean isChainedImportOnStack(ConfigurationClass configClass) { if (this.importStack.contains(configClass)) { String configClassName = configClass.getMetadata().getClassName(); AnnotationMetadata importingClass = this.importStack.getImportingClassFor(configClassName); while (importingClass != null) { if (configClassName.equals(importingClass.getClassName())) { return true; } importingClass = this.importStack.getImportingClassFor(importingClass.getClassName()); } } return false; } /** * Factory method to obtain a {@link SourceClass} from a {@link ConfigurationClass}. */ private SourceClass asSourceClass(ConfigurationClass configurationClass, Predicate filter) throws IOException { AnnotationMetadata metadata = configurationClass.getMetadata(); if (metadata instanceof StandardAnnotationMetadata standardAnnotationMetadata) { return asSourceClass(standardAnnotationMetadata.getIntrospectedClass(), filter); } return asSourceClass(metadata.getClassName(), filter); } /** * Factory method to obtain a {@link SourceClass} from a {@link Class}. */ SourceClass asSourceClass(@Nullable Class classType, Predicate filter) throws IOException { if (classType == null || filter.test(classType.getName())) { return this.objectSourceClass; } try { // Sanity test that we can reflectively read annotations, // including Class attributes; if not -> fall back to ASM for (Annotation ann : classType.getDeclaredAnnotations()) { AnnotationUtils.validateAnnotation(ann); } return new SourceClass(classType); } catch (Throwable ex) { // Enforce ASM via class name resolution return asSourceClass(classType.getName(), filter); } } /** * Factory method to obtain a {@link SourceClass} collection from class names. */ private Collection asSourceClasses(String[] classNames, Predicate filter) throws IOException { List annotatedClasses = new ArrayList<>(classNames.length); for (String className : classNames) { SourceClass sourceClass = asSourceClass(className, filter); if (this.objectSourceClass != sourceClass) { annotatedClasses.add(sourceClass); } } return annotatedClasses; } /** * Factory method to obtain a {@link SourceClass} from a class name. */ 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())); } catch (ClassNotFoundException ex) { throw new IOException("Failed to load class [" + className + "]", ex); } } return new SourceClass(this.metadataReaderFactory.getMetadataReader(className)); } private List collectRegisterBeanConditions(ConfigurationClass configurationClass) { AnnotationMetadata metadata = configurationClass.getMetadata(); List allConditions = new ArrayList<>(this.conditionEvaluator.collectConditions(metadata)); ConfigurationClass enclosingConfigurationClass = getEnclosingConfigurationClass(configurationClass); if (enclosingConfigurationClass != null) { allConditions.addAll(this.conditionEvaluator.collectConditions(enclosingConfigurationClass.getMetadata())); } return allConditions.stream().filter(REGISTER_BEAN_CONDITION_FILTER).toList(); } @Nullable private ConfigurationClass getEnclosingConfigurationClass(ConfigurationClass configurationClass) { String enclosingClassName = configurationClass.getMetadata().getEnclosingClassName(); if (enclosingClassName != null) { return configurationClass.getImportedBy().stream() .filter(candidate -> enclosingClassName.equals(candidate.getMetadata().getClassName())) .findFirst().orElse(null); } return null; } @SuppressWarnings("serial") private class ImportStack extends ArrayDeque implements ImportRegistry { private final MultiValueMap imports = new LinkedMultiValueMap<>(); void registerImport(AnnotationMetadata importingClass, String importedClass) { this.imports.add(importedClass, importingClass); } @Override @Nullable public AnnotationMetadata getImportingClassFor(String importedClass) { return CollectionUtils.lastElement(this.imports.get(importedClass)); } @Override public void removeImportingClass(String importingClass) { for (List list : this.imports.values()) { for (Iterator iterator = list.iterator(); iterator.hasNext();) { if (iterator.next().getClassName().equals(importingClass)) { iterator.remove(); break; } } } removeKnownSuperclass(importingClass, true); } /** * Given a stack containing (in order) *

    *
  • com.acme.Foo
  • *
  • com.acme.Bar
  • *
  • com.acme.Baz
  • *
* return "[Foo->Bar->Baz]". */ @Override public String toString() { StringJoiner joiner = new StringJoiner("->", "[", "]"); for (ConfigurationClass configurationClass : this) { joiner.add(configurationClass.getSimpleName()); } return joiner.toString(); } } private class DeferredImportSelectorHandler { @Nullable private List deferredImportSelectors = new ArrayList<>(); /** * Handle the specified {@link DeferredImportSelector}. If deferred import * selectors are being collected, this registers this instance to the list. If * they are being processed, the {@link DeferredImportSelector} is also processed * immediately according to its {@link DeferredImportSelector.Group}. * @param configClass the source configuration class * @param importSelector the selector to handle */ void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) { DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector); if (this.deferredImportSelectors == null) { DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler(); handler.register(holder); handler.processGroupImports(); } else { this.deferredImportSelectors.add(holder); } } void process() { List deferredImports = this.deferredImportSelectors; this.deferredImportSelectors = null; try { if (deferredImports != null) { DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler(); deferredImports.sort(DEFERRED_IMPORT_COMPARATOR); deferredImports.forEach(handler::register); handler.processGroupImports(); } } finally { this.deferredImportSelectors = new ArrayList<>(); } } } private class DeferredImportSelectorGroupingHandler { private final Map groupings = new LinkedHashMap<>(); private final Map configurationClasses = new HashMap<>(); void register(DeferredImportSelectorHolder deferredImport) { Class group = deferredImport.getImportSelector().getImportGroup(); DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent( (group != null ? group : deferredImport), key -> new DeferredImportSelectorGrouping(createGroup(group))); grouping.add(deferredImport); this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getConfigurationClass()); } @SuppressWarnings("NullAway") void processGroupImports() { for (DeferredImportSelectorGrouping grouping : this.groupings.values()) { Predicate filter = grouping.getCandidateFilter(); grouping.getImports().forEach(entry -> { ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata()); try { processImports(configurationClass, asSourceClass(configurationClass, filter), Collections.singleton(asSourceClass(entry.getImportClassName(), filter)), filter, false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configurationClass.getMetadata().getClassName() + "]", ex); } }); } } private Group createGroup(@Nullable Class type) { Class effectiveType = (type != null ? type : DefaultDeferredImportSelectorGroup.class); return ParserStrategyUtils.instantiateClass(effectiveType, Group.class, ConfigurationClassParser.this.environment, ConfigurationClassParser.this.resourceLoader, ConfigurationClassParser.this.registry); } } private static class DeferredImportSelectorHolder { private final ConfigurationClass configurationClass; private final DeferredImportSelector importSelector; DeferredImportSelectorHolder(ConfigurationClass configClass, DeferredImportSelector selector) { this.configurationClass = configClass; this.importSelector = selector; } ConfigurationClass getConfigurationClass() { return this.configurationClass; } DeferredImportSelector getImportSelector() { return this.importSelector; } } private static class DeferredImportSelectorGrouping { private final DeferredImportSelector.Group group; private final List deferredImports = new ArrayList<>(); DeferredImportSelectorGrouping(Group group) { this.group = group; } void add(DeferredImportSelectorHolder deferredImport) { this.deferredImports.add(deferredImport); } /** * Return the imports defined by the group. * @return each import with its associated configuration class */ Iterable getImports() { for (DeferredImportSelectorHolder deferredImport : this.deferredImports) { this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector()); } return this.group.selectImports(); } Predicate getCandidateFilter() { Predicate mergedFilter = DEFAULT_EXCLUSION_FILTER; for (DeferredImportSelectorHolder deferredImport : this.deferredImports) { Predicate selectorFilter = deferredImport.getImportSelector().getExclusionFilter(); if (selectorFilter != null) { mergedFilter = mergedFilter.or(selectorFilter); } } return mergedFilter; } } private static class DefaultDeferredImportSelectorGroup implements Group { private final List imports = new ArrayList<>(); @Override public void process(AnnotationMetadata metadata, DeferredImportSelector selector) { for (String importClassName : selector.selectImports(metadata)) { this.imports.add(new Entry(metadata, importClassName)); } } @Override public Iterable selectImports() { return this.imports; } } /** * Simple wrapper that allows annotated source classes to be dealt with * in a uniform manner, regardless of how they are loaded. */ private class SourceClass implements Ordered { private final Object source; // Class or MetadataReader private final AnnotationMetadata metadata; public SourceClass(Object source) { this.source = source; if (source instanceof Class sourceClass) { this.metadata = AnnotationMetadata.introspect(sourceClass); } else { this.metadata = ((MetadataReader) source).getAnnotationMetadata(); } } public final AnnotationMetadata getMetadata() { return this.metadata; } @Override public int getOrder() { Integer order = ConfigurationClassUtils.getOrder(this.metadata); return (order != null ? order : Ordered.LOWEST_PRECEDENCE); } public Class loadClass() throws ClassNotFoundException { if (this.source instanceof Class sourceClass) { return sourceClass; } String className = ((MetadataReader) this.source).getClassMetadata().getClassName(); return ClassUtils.forName(className, resourceLoader.getClassLoader()); } public boolean isAssignable(Class clazz) throws IOException { if (this.source instanceof Class sourceClass) { return clazz.isAssignableFrom(sourceClass); } return new AssignableTypeFilter(clazz).match((MetadataReader) this.source, metadataReaderFactory); } public ConfigurationClass asConfigClass(ConfigurationClass importedBy) { if (this.source instanceof Class sourceClass) { return new ConfigurationClass(sourceClass, importedBy); } return new ConfigurationClass((MetadataReader) this.source, importedBy); } public Collection getMemberClasses() throws IOException { Object sourceToProcess = this.source; if (sourceToProcess instanceof Class sourceClass) { try { Class[] declaredClasses = sourceClass.getDeclaredClasses(); List members = new ArrayList<>(declaredClasses.length); for (Class declaredClass : declaredClasses) { members.add(asSourceClass(declaredClass, DEFAULT_EXCLUSION_FILTER)); } return members; } catch (NoClassDefFoundError err) { // getDeclaredClasses() failed because of non-resolvable dependencies // -> fall back to ASM below sourceToProcess = metadataReaderFactory.getMetadataReader(sourceClass.getName()); } } // ASM-based resolution - safe for non-resolvable classes as well MetadataReader sourceReader = (MetadataReader) sourceToProcess; String[] memberClassNames = sourceReader.getClassMetadata().getMemberClassNames(); List members = new ArrayList<>(memberClassNames.length); for (String memberClassName : memberClassNames) { try { members.add(asSourceClass(memberClassName, DEFAULT_EXCLUSION_FILTER)); } catch (IOException ex) { // Let's skip it if it's not resolvable - we're just looking for candidates if (logger.isDebugEnabled()) { logger.debug("Failed to resolve member class [" + memberClassName + "] - not considering it as a configuration class candidate"); } } } return members; } public SourceClass getSuperClass() throws IOException { if (this.source instanceof Class sourceClass) { return asSourceClass(sourceClass.getSuperclass(), DEFAULT_EXCLUSION_FILTER); } return asSourceClass( ((MetadataReader) this.source).getClassMetadata().getSuperClassName(), DEFAULT_EXCLUSION_FILTER); } public Set getInterfaces() throws IOException { Set result = new LinkedHashSet<>(); if (this.source instanceof Class sourceClass) { for (Class ifcClass : sourceClass.getInterfaces()) { result.add(asSourceClass(ifcClass, DEFAULT_EXCLUSION_FILTER)); } } else { for (String className : this.metadata.getInterfaceNames()) { result.add(asSourceClass(className, DEFAULT_EXCLUSION_FILTER)); } } return result; } public Set getAnnotations() { Set result = new LinkedHashSet<>(); if (this.source instanceof Class sourceClass) { for (Annotation ann : sourceClass.getDeclaredAnnotations()) { Class annType = ann.annotationType(); if (!annType.getName().startsWith("java")) { try { result.add(asSourceClass(annType, DEFAULT_EXCLUSION_FILTER)); } catch (Throwable ex) { // An annotation not present on the classpath is being ignored // by the JVM's class loading -> ignore here as well. } } } } else { for (String className : this.metadata.getAnnotationTypes()) { if (!className.startsWith("java")) { try { result.add(getRelated(className)); } catch (Throwable ex) { // An annotation not present on the classpath is being ignored // by the JVM's class loading -> ignore here as well. } } } } return result; } public Collection getAnnotationAttributes(String annType, String attribute) throws IOException { Map annotationAttributes = this.metadata.getAnnotationAttributes(annType, true); if (annotationAttributes == null || !annotationAttributes.containsKey(attribute)) { return Collections.emptySet(); } String[] classNames = (String[]) annotationAttributes.get(attribute); Set result = CollectionUtils.newLinkedHashSet(classNames.length); for (String className : classNames) { result.add(getRelated(className)); } return result; } private SourceClass getRelated(String className) throws IOException { if (this.source instanceof Class sourceClass) { try { Class clazz = ClassUtils.forName(className, sourceClass.getClassLoader()); return asSourceClass(clazz, DEFAULT_EXCLUSION_FILTER); } catch (ClassNotFoundException ex) { // Ignore -> fall back to ASM next, except for core java types. if (className.startsWith("java")) { throw new IOException("Failed to load class [" + className + "]", ex); } return new SourceClass(metadataReaderFactory.getMetadataReader(className)); } } return asSourceClass(className, DEFAULT_EXCLUSION_FILTER); } @Override public boolean equals(@Nullable Object other) { return (this == other || (other instanceof SourceClass that && this.metadata.getClassName().equals(that.metadata.getClassName()))); } @Override public int hashCode() { return this.metadata.getClassName().hashCode(); } @Override public String toString() { return this.metadata.getClassName(); } } /** * {@link Problem} registered upon detection of a circular {@link Import}. */ private static class CircularImportProblem extends Problem { public CircularImportProblem(ConfigurationClass attemptedImport, Deque importStack) { super(String.format("A circular @Import has been detected: " + "Illegal attempt by @Configuration class '%s' to import class '%s' as '%s' is " + "already present in the current import stack %s", importStack.element().getSimpleName(), attemptedImport.getSimpleName(), attemptedImport.getSimpleName(), importStack), new Location(importStack.element().getResource(), attemptedImport.getMetadata())); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy