ConfigurationClassPostProcessor handles all configuration classes, including @ Component, @ Bean, @ Import annotation, etc. Since the configuration class may introduce new configuration classes and the new configuration classes need to be processed, the ConfigurationClassPostProcessor uses a do while loop to process the configuration classes until there are no unprocessed configuration classes.
The specific processing of the configuration class is entrusted to the ConfigurationClassParser. The introduction of a new configuration class into the configuration class is mainly divided into two cases:
1. The Member Class of the configuration class is the configuration class, @ ComponentScan scans the configuration class, etc. these new configuration classes imported into rigistry will continue to be processed recursively and put into the configurationClasses attribute of ConfigurationClassParser. These have been processed
2. @ Bean, @ ImportResource, ImportBeanDefinitionRegistrar, etc. are not directly registered in the registry, but loaded into the registry by the configurationclassbeandefinitionreader. These configuration classes have not been processed
Configuration class processing core logic
The core logic here is relatively simple. As mentioned above, it is mainly in the ConfigurationClassPostProcessor#processConfigBeanDefinitions method
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { List<BeanDefinitionHolder> configCandidates = new ArrayList<>(); String[] candidateNames = registry.getBeanDefinitionNames(); // Filter out the configuration classes in beanFactory for (String beanName : candidateNames) { BeanDefinition beanDef = registry.getBeanDefinition(beanName); if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) { if (logger.isDebugEnabled()) { logger.debug("Bean definition has already been processed as a configuration class: " + beanDef); } } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) { configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); } } // Return immediately if no @Configuration classes were found if (configCandidates.isEmpty()) { return; } // sort configCandidates.sort((bd1, bd2) -> { int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition()); int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition()); return Integer.compare(i1, i2); }); // Detect any custom bean name generation strategy supplied through the enclosing application context SingletonBeanRegistry sbr = null; if (registry instanceof SingletonBeanRegistry) { sbr = (SingletonBeanRegistry) registry; if (!this.localBeanNameGeneratorSet) { BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton( AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR); if (generator != null) { this.componentScanBeanNameGenerator = generator; this.importBeanNameGenerator = generator; } } } if (this.environment == null) { this.environment = new StandardEnvironment(); } ConfigurationClassParser parser = new ConfigurationClassParser( this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); // candidates: the configuration class to be resolved, and the configuration class already resolved by alreadyParsed Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); do { StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse"); // Handle each configuration class, @ Component, @ Import, @ ComponentScan, @ Bean annotation, etc. here parser.parse(candidates); parser.validate(); // parser. The configuration classes obtained by getconfigurationclasses () have been processed Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses()); configClasses.removeAll(alreadyParsed); // Read the model and create bean definitions based on its content if (this.reader == null) { this.reader = new ConfigurationClassBeanDefinitionReader( registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry()); } // loadBeanDefinitions, @ bean, @ ImportResource, ImportBeanDefinitionRegistrar register beans // load to registry here. This part of the configuration class is not processed, which will be described in detail later this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end(); candidates.clear(); // It indicates that a new bean is imported, and there may be configuration classes that are not parse d if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames)); Set<String> alreadyParsedClasses = new HashSet<>(); for (ConfigurationClass configurationClass : alreadyParsed) { alreadyParsedClasses.add(configurationClass.getMetadata().getClassName()); } // Put the un parse d configuration classes into candidates and handle them in the next round of do while loop for (String candidateName : newCandidateNames) { if (!oldCandidateNames.contains(candidateName)) { BeanDefinition bd = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(new BeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; } } while (!candidates.isEmpty()); // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) { sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry()); } if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) { // Clear cache in externally provided MetadataReaderFactory; this is a no-op // for a shared cache since it'll be cleared by the ApplicationContext. ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache(); } }
The following focuses on two important steps in the do while loop: parse and loadbean definitions
parser.parse(candidates)
In the outer do while loop, the specific work of a group of configuration class parsing is handed over to the ConfigurationClassParser. Recursive calls exist in the processing of this class, which is complex. The call link diagram is as follows
Resolve a set of configuration classes
parse(Set) handles a set of configuration classes that are called in ConfigurationClassPostProcessor loop in do-while. This method resolves all configuration classes one by one in the for cycle, and finally calls deferredImportSelectorHandler to handle DeferredImportSelector.
public void parse(Set<BeanDefinitionHolder> configCandidates) { for (BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); try { if (bd instanceof AnnotatedBeanDefinition) { parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName()); } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) { parse(((AbstractBeanDefinition) bd).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(); }
Resolve a configuration class
The three parse methods all call the processConfigurationClass method. Parse is as follows, which only unifies different input parameters into ConfigurationClass
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), DEFAULT_EXCLUSION_FILTER); } protected final void parse(Class<?> clazz, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER); } protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException { processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER); }
The processConfigurationClass method handles three things
1. If existingClass exists in the current configClass (the configuration class has been processed), if existingClass and configClass are imported by other configuration classes, the imported classes will be merged and configClass will not be processed; If configClass is not imported, reprocess the class
2. The do while loop traverses all the parent classes of a configuration class and calls doProcessConfigurationClass to process the configuration class and each parent class
3. Add Map configurationClasses after processing the configuration classes
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException { 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 { // Explicit bean definition found, probably replacing an import. // Let's remove the old one and go with the new one. this.configurationClasses.remove(configClass); this.knownSuperclasses.values().removeIf(configClass::equals); } } // Recursively process the configuration class and its superclass hierarchy. SourceClass sourceClass = asSourceClass(configClass, filter); do { sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter); } while (sourceClass != null); this.configurationClasses.put(configClass, configClass); }
Specific resolution of configuration class
doProcessConfigurationClass specifically handles each configuration class or its parent class. The configuration class is configClass, and the class to be processed is sourceClass. doProcessConfigurationClass mainly obtains the annotations on sourceClass class, interface, method and attribute, and performs specific processing, which are described below
1. Processing MemberClasses
The @ member class will be marked internally
if (configClass.getMetadata().isAnnotated(Component.class.getName())) { processMemberClasses(configClass, sourceClass, filter); }
processMemberClasses gets all the memberclasses of the sourceClass that are configuration classes, and calls processConfigurationClass to process them as configuration classes, candidate Asconfigclass (configclass) specifies which configuration class the internal class is imported from. There will be recursive calls
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()); 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(); } } } } }
2. Process @ PropertySource
PropertySource is used to import configuration files
3. Process @ ComponentScan
Scan bean s through componentScanParser and call parse(String, String) overloaded method to parse the scanned configuration class. There are also recursive calls here
// Process any @ComponentScan annotations Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (!componentScans.isEmpty() && !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately Set<BeanDefinitionHolder> 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()); } } } }
4. Process @ Import
Processing @ Import annotation is complicated. First, find the imported classes from sourceClass, and then process these classes according to the situation.
Find Import class
Find all imported classes through tail recursion
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException { Set<SourceClass> imports = new LinkedHashSet<>(); Set<SourceClass> visited = new LinkedHashSet<>(); collectImports(sourceClass, imports, visited); return imports; }
Recursive method collectImports. Get all annotations on sourceClass. If the annotation is not @ Import, continue to search collectImports to find @ Import on the annotation; If the annotation is @ Import, get the class imported and add it to imports
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> 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")); } }
Import class processing
The for loop handles all imported classes in four cases
- If it is DeferredImportSelector, call deferredImportSelectorHandler#handle for processing. Details will be described later
- If it is an ordinary ImportSelector, instantiate the Selector and selectImport, and call processImports to recursively process these classes
- If it is importbeandefinitionregister, call configclass#addimportbeandefinitionregister to temporarily store the register, and then call the register registrat ion class through ConfigurationClassBeanDefinitionReader
- If none of the above is the case, call processConfigurationClass as a configuration class for processing
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter, 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<String> selectorFilter = selector.getExclusionFilter(); if (selectorFilter != null) { exclusionFilter = exclusionFilter.or(selectorFilter); } if (selector instanceof DeferredImportSelector) { this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector); } else { String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata()); Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter); processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, 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), exclusionFilter); } } } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", ex); } finally { this.importStack.pop(); } } }
5. Process @ ImportResource
BeanDefinition may be defined in the resource. Import the resource through this annotation. When processing this annotation, only temporarily store the resource in the configClass, and then register the BeanDefinition through the ConfigurationClassBeanDefinitionReader
AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } }
6. Process @ Bean
When processing @ Bean, first resolve the beanMethod, then temporarily store it in the configClass, and then register BeanDifinition through the ConfigurationClassBeanDefinitionReader
// Process individual @Bean methods Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); }
7. Processing interface
Recursively handle @ Bean annotations of all interfaces and parent interfaces
private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { for (SourceClass ifc : sourceClass.getInterfaces()) { Set<MethodMetadata> 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); } }
deferredImportSelectorHandler
deferredImportSelectorHandler is used to process DeferredImportSelector. There are two important methods:
1. If DeferredImportSelector is encountered during processImports, call deferredImportSelectorHandler#handle for processing
2. When parse configures classes, after all configuration classes are processed, call the deferredImportSelectorHandler#process method to handle DeferredImportSelector uniformly
Difference between deffered ImportSelector and ImportSelector
When processorImports encounters ImportSelector, it will immediately selectImport; For the DefferedImportSelector, it will be temporarily stored, and the DefferedImportSelector will be processed uniformly after other configuration classes are processed. It is useful when registering beanDefinition with the @ Conditional annotation.
handle and process methods
The purpose of the handler and process methods is to implement the deffered semantics, that is, delay processing until other configuration classes of the current parse process have been processed, and then process the DefferedImportSelector.
handle method
The handle method will be called in the process of processImports. If deferredImportSelectors == null, it means that the process is in progress (deferredImportSelectors will be set to null before starting the process. When this method is called, DeferredImportSelector is imported in the process of processing DeferredImportSelector). At this time, it will be processed directly, DeferredImportSelector; Otherwise, add it directly to the list deferredImportSelectors and wait for the process to process
process method
Set deferredImportSelectors to null before starting the process. Then instantiate the DeferredImportSelectorGroupingHandler, register all the DefferedImportSelector into handler, and then call DeferredImportSelectorGroupingHandler#processGroupImports.
private class DeferredImportSelectorHandler { // Deferred importselector for staging @Nullable private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>(); public 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); } } public void process() { List<DeferredImportSelectorHolder> 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<>(); } } }
DeferredImportSelectorGroupingHandler
In the process, the processing logic is delegated to DeferredImportSelectorGroupingHandler, first register, and then processGroupImports. The purpose is to realize the Group semantics: that is, multiple deferredimportselectors are a Group, and the classes imported by these deferredimportselectors can be put together to determine which ones are ultimately imported through specific logic
First look at the DeferredImportSelector interface definition
DeferredImportSelector
public interface DeferredImportSelector extends ImportSelector { // Same class <? Extensions group > is the same group, and null is defaultdefaultdefaultimportselectorgroup @Nullable default Class<? extends Group> getImportGroup() { return null; } interface Group { // If a group contains multiple deferredimportselectors, this method will be called to process them one by one. Temporary storage is required during processing // Each deferredimportselector imports the incoming class void process(AnnotationMetadata metadata, DeferredImportSelector selector); // After calling process to process all deferredimportselectors, this method will be called to obtain the class to be imported finally, // This can implement specific filtering logic or other logic Iterable<Entry> selectImports(); // Encapsulates a className of import. Metadata is the metadata of the configuration class, and importClassName is the metadata to be imported // className of class Entry { private final AnnotationMetadata metadata; private final String importClassName; public Entry(AnnotationMetadata metadata, String importClassName) { this.metadata = metadata; this.importClassName = importClassName; } } }
The Group semantics is implemented in the DeferredImportSelectorGrouping class, as follows
DeferredImportSelectorGrouping
DeferredImportSelectorGrouping encapsulates Group instances and multiple deferredimportselectors. getImports calls group#process to process all DeferredImportSelector one by one, and then calls group#selectImports to get all classes of import.
private static class DeferredImportSelectorGrouping { private final DeferredImportSelector.Group group; private final List<DeferredImportSelectorHolder> deferredImports = new ArrayList<>(); DeferredImportSelectorGrouping(Group group) { this.group = group; } public void add(DeferredImportSelectorHolder deferredImport) { this.deferredImports.add(deferredImport); } public Iterable<Group.Entry> getImports() { for (DeferredImportSelectorHolder deferredImport : this.deferredImports) { this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector()); } return this.group.selectImports(); } }
The default Group is defaultdefaultdefaultimportselectorgroup
The process method simply calls the selector#selectImports method and stores it temporarily; The selectImports method returns all the classes imported by the selector
private static class DefaultDeferredImportSelectorGroup implements Group { private final List<Entry> 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<Entry> selectImports() { return this.imports; } }
Another implementation is AutoConfigurationGroup
The process method is also a class that temporarily stores all imports; selectImports contains specific filtering logic
private static class AutoConfigurationGroup implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware { private final Map<String, AnnotationMetadata> entries = new LinkedHashMap<>(); private final List<AutoConfigurationEntry> autoConfigurationEntries = new ArrayList<>(); @Override public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) { Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector, () -> String.format("Only %s implementations are supported, got %s", AutoConfigurationImportSelector.class.getSimpleName(), deferredImportSelector.getClass().getName())); AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector) .getAutoConfigurationEntry(annotationMetadata); this.autoConfigurationEntries.add(autoConfigurationEntry); for (String importClassName : autoConfigurationEntry.getConfigurations()) { this.entries.putIfAbsent(importClassName, annotationMetadata); } } @Override public Iterable<Entry> selectImports() { if (this.autoConfigurationEntries.isEmpty()) { return Collections.emptyList(); } Set<String> allExclusions = this.autoConfigurationEntries.stream() .map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet()); Set<String> processedConfigurations = this.autoConfigurationEntries.stream() .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream) .collect(Collectors.toCollection(LinkedHashSet::new)); processedConfigurations.removeAll(allExclusions); return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream() .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName)) .collect(Collectors.toList()); } }
Finally, take a look at the register method and processGroupImports method of DeferredImportSelectorGroupingHandler
DeferredImportSelectorGroupingHandler implementation details
groupings property
The handler will process all deferredimportselectors. This map saves deferredimportselectors in groups
Key: if the group of DeferredImportSelector is not null, then key is the Class object of the group; If group is null, then key is the DeferredImportSelectorHolder object. The key itself has no effect. It only divides all deferredimportselectors into groups through the key, and then groups Values() to traverse
value: is the DeferredImportSelectorGrouping object
private class DeferredImportSelectorGroupingHandler { private final Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>(); private final Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>(); // Group deferredimportselectors and create DeferredImportSelectorGrouping public void register(DeferredImportSelectorHolder deferredImport) { Class<? extends Group> 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()); } public void processGroupImports() { // Group the DeferredImportSelector that has been grouped for (DeferredImportSelectorGrouping grouping : this.groupings.values()) { Predicate<String> exclusionFilter = grouping.getCandidateFilter(); // DeferredImportSelectorGrouping#getImports method called. As above, it calls // group#process and group#selectImports, which are obtained through the specific logic of the group // Class of DeferredImportSelector import. Then forEach passes processImports for all classes // handle grouping.getImports().forEach(entry -> { ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata()); try { processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter), Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)), exclusionFilter, false); } catch (BeanDefinitionStoreException ex) { throw ex; } catch (Throwable ex) { throw new BeanDefinitionStoreException( "Failed to process import candidates for configuration class [" + configurationClass.getMetadata().getClassName() + "]", ex); } }); } } // Instantiate a group. If the DeferredImportSelector specifies a group class, instantiate it with the specified class. Otherwise, it is the default // DefaultDeferredImportSelectorGroup private Group createGroup(@Nullable Class<? extends Group> type) { Class<? extends Group> effectiveType = (type != null ? type : DefaultDeferredImportSelectorGroup.class); return ParserStrategyUtils.instantiateClass(effectiveType, Group.class, ConfigurationClassParser.this.environment, ConfigurationClassParser.this.resourceLoader, ConfigurationClassParser.this.registry); } }
reader.loadBeanDefinitions(configClasses)
In the process of parse, the imported classes in several places will be temporarily stored in configClass, and then loaded into beanFactory by configurationclassbeandefinitionreader
1. ImportBeanDefinitionRegistrar encountered during processImports
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()); }
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) { registrars.forEach((registrar, metadata) -> registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator)); }
2,@ImportResource
AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource != null) { String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); } }
If the @ ImportResource annotation specifies the class name of BeanDefinitionReader, GroovyBeanDefinitionReader, XmlBeanDefinitionReader and reader are used to load according to the suffix of the configuration file
private void loadBeanDefinitionsFromImportedResources( Map<String, Class<? extends BeanDefinitionReader>> importedResources) { Map<Class<?>, BeanDefinitionReader> readerInstanceCache = new HashMap<>(); importedResources.forEach((resource, readerClass) -> { // Default reader selection necessary? if (BeanDefinitionReader.class == readerClass) { if (StringUtils.endsWithIgnoreCase(resource, ".groovy")) { // When clearly asking for Groovy, that's what they'll get... readerClass = GroovyBeanDefinitionReader.class; } else if (shouldIgnoreXml) { throw new UnsupportedOperationException("XML support disabled"); } else { // Primarily ".xml" files but for any other extension as well readerClass = XmlBeanDefinitionReader.class; } } BeanDefinitionReader reader = readerInstanceCache.get(readerClass); if (reader == null) { try { // Instantiate the specified BeanDefinitionReader reader = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry); // Delegate the current ResourceLoader to it if possible if (reader instanceof AbstractBeanDefinitionReader) { AbstractBeanDefinitionReader abdr = ((AbstractBeanDefinitionReader) reader); abdr.setResourceLoader(this.resourceLoader); abdr.setEnvironment(this.environment); } readerInstanceCache.put(readerClass, reader); } catch (Throwable ex) { throw new IllegalStateException( "Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]"); } } // TODO SPR-6310: qualify relative path locations as done in AbstractContextLoader.modifyLocations reader.loadBeanDefinitions(resource); }); }
3,@Bean
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); }