springboot auto scan added BeanDefinition source code analysis

Posted by amit.patel on Tue, 15 Feb 2022 04:19:00 +0100

1.

During springboot startup, the bean definitions to be loaded will be collected and added to BeanFactory as BeanDefinition objects.

Since there are only getBean and other methods to obtain bean objects in BeanFactory, adding BeanDefinition to BeanFactory is through void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException of BeanDefinitionRegistry interface; Method to complete.

Therefore, if the implementation class of our BeanFactory needs to have the ability to return bean objects and add or delete BeanDefinition through beanName, it should at least implement the two interfaces of BeanFactory and BeanDefinitionRegistry.

Here, let's take a look at how springboot finds the bean definition and adds it to BeanFactory.

Since we only focus on finding the definition of bean objects here, the BeanFactory mentioned here will mainly focus on the interface BeanDefinitionRegistry.

We mainly analyze the configuration of spring boot scanning and loading bean s locally, which has little to do with our code, so let's use the simplest code. The specific codes are as follows:

package com.example.bootargs;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BootargsApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootargsApplication.class, args);
    }

}

The main class unification mentioned later is com example. bootargs. BootargsApplication

2.

The definition of Springboot lookup bean is mainly completed through the class ConfigurationClassPostProcessor.

ConfigurationClassPostProcessor implements the BeanDefinitionRegistryPostProcessor interface. The BeanDefinitionRegistryPostProcessor interface adds bean definitions to the implementation class of BeanDefinitionRegistry through the postProcessBeanDefinitionRegistry method.

BeanDefinitionRegistryPostProcessor inherits the BeanFactory postprocessor interface, which is mainly used to enhance BeanFactory. In the springboot startup process, BeanFactory will be created first, and then beanfactoryprocessor will be called to the BeanFactory

After enhancement, the bean object will be created finally.

BeanFactory is enhanced through BeanFactory postprocessor, mainly through the static method of postprocessor registrationdelegate. In this process, the ConfigurationClassPostProcessor class will be called.

Since ConfigurationClassPostProcessor implements the BeanDefinitionRegistryPostProcessor interface, PostProcessorRegistrationDelegate will call the postProcessBeanDefinitionRegistry method of ConfigurationClassPostProcessor and call the processConfigBeanDefinitions method to find the bean definition. Let's look at it from here as an entrance.

3.

Let's take a look at the processConfigBeanDefinitions method of ConfigurationClassPostProcessor

	/**
	 * Build and validate a configuration model based on the registry of
	 * {@link Configuration} classes.
	 */
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();
		//In the following for loop, the Configuration class with Configuration annotation will be found from the definition of existing beans in beanFactory.
    //By default, only one main class containing the spring bootapplication annotation is obtained here
		for (String beanName : candidateNames) {
				......
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}
		
    //If the configuration class is not found, it is returned directly
		// Return immediately if no @Configuration classes were found
		if (configCandidates.isEmpty()) {
			return;
		}

		......
		//Here, you can use the ConfigurationClassParser to resolve the configuration class
		// Parse each @Configuration class
		ConfigurationClassParser parser = new ConfigurationClassParser(
				this.metadataReaderFactory, this.problemReporter, this.environment,
				this.resourceLoader, this.componentScanBeanNameGenerator, registry);

		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
			//The search of all bean definitions is done here. Let's take a look at the parse method here
      parser.parse(candidates);
			......
	}

In the parse method of ConfigurationClassParser, because our configuration class is defined through annotation, we will take the branch of AnnotatedBeanDefinition. Continue to call processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER); Let's go directly to the processConfigurationClass method.

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
  	//Here, first check whether there is a Conditional annotation on the configuration class. If so, resolve it to see if you want to skip this annotation class
		if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
			return;
		}
		
  	//All resolved configuration classes should be placed in configurationClasses. key is the currently resolved configuration class, and value indicates who imported the configuration class.
  	//If this configuration class is not imported through other classes, then the key and value are the same.
		ConfigurationClass existingClass = this.configurationClasses.get(configClass);
  	//If the same configuration class is imported through multiple configuration classes, the import relationship between this configuration class and the configuration class needs to be merged
		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.
  	//Here is to convert the configuration class into a SourceClass object
		SourceClass sourceClass = asSourceClass(configClass, filter);
		do {
      //Here, the real configuration class will be parsed.
      
      //Note that this is a do while loop. After processing the current configuration class, it will continue to process the parent class of the current configuration class.
      //If the parent class name of the current class does not start with java and has not been processed, it will continue to be processed in this do while loop
      
			sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
		}
		while (sourceClass != null);
		
		this.configurationClasses.put(configClass, configClass);
	}

this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION) is filtered mainly through org springframework. context. annotation. Subclasses of the condition interface to implement the matches method.

For example:

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration 

The above is the definition of MessageSourceAutoConfiguration class. First, we will find the Conditional annotation on it, and we will find two annotations:

  • @ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)

    Since the annotation has @ Conditional(OnBeanCondition.class), it will be handed over to the OnBeanCondition class for processing.

  • @Conditional(ResourceBundleCondition.class) will be handed over to ResourceBundleCondition class for processing.

The processConfigurationClass method can be found in several places, mainly in three places:

  1. This processConfigurationClass method will be called when the parse method is called.
  2. The processConfigurationClass method may also be called multiple times when resolving the properties of the current configuration class in doProcessConfigurationClass.
  3. In this deferredImportSelectorHandler. The processConfigurationClass method may also be called when process() is called

All the configuration classes we resolve here will be added to and called configurationClasses Put (configclass, configclass) method, so when we finally add multiple classes to the configurationClasses collection, we will call the processConfigurationClass method at least as many times (judging by the Conditional annotation, so the number of calls may be more than the number of elements finally added to the configurationClasses Collection)

	@Nullable
	protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {
		//Here, check whether the class has Component annotation. If so, find the internal class of the current class for processing
		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
      //Here, the processConfigurationClass method above may be called recursively
			processMemberClasses(configClass, sourceClass, filter);
		}

		// Process any @PropertySource annotations
    //Here, check whether the class has PropertySources annotation. If so, resolve the property configuration and add it to the environment context
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
			else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}

		// Process any @ComponentScan annotations
    //Here, check whether the class has ComponentScans annotation. If so, scan the directory according to the conditions here to find the bean definition
    //Because our current class has the SpringBootApplication annotation, we can find the ComponentScan annotation here, and we will go into this method
		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
        //Here, we will deal with the contents related to the ComponentScans annotation.
        //There is a basePackages attribute on the ComponentScans annotation, which is used to specify the name of the scanned package.
        //If the basePackages attribute is not specified, the definition of the relevant bean will be found under the package of the current class and all its sub packages.
        //We generally do not specify the basePackages attribute, so we will find the bean definition under the package of the current sourceClass class and all its sub packages.
        //The controller,service,dao, etc. defined in our own code are obtained from the bean definition in this step.
				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)) {	
            //The processConfigurationClass method will also be called indirectly here
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}

		// Process any @Import annotations
    //Here, we will deal with the import annotation on the class.
    
    //getImports(sourceClass) will first get the class of import.
    //There will be two. One is autoconfigurationpackages annotated on AutoConfigurationPackage Registrar. class
    //The other is the annotation autoconfigurationimportselector on EnableAutoConfiguration class)
    
    //Let's take a look at the method processImports
   	//The processConfigurationClass method may also be called
		processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
		......
	}

doProcessConfigurationClass is really used to process configuration classes.

In this method, the internal class, PropertySources annotation, ComponentScans annotation, Import annotation, ImportResource annotation, Bean annotation, default method on the interface and continue to recurse to its parent class will be processed in turn.

Of which:

  • The inner class will continue to call the processConfigurationClass method to process recursively

  • The PropertySources annotation is added to the environment context after parsing

  • The class scanned by the ComponentScans annotation will be directly added to beanFactory, and the processConfigurationClass method will continue to be called for recursive processing

  • The Import annotation will be processed in three cases:

    • If the class of Import implements ImportSelector. If it implements its sub interface DeferredImportSelector, it will be added to deferredImportSelectors for subsequent processing. If the sub interface is not implemented, processImports will be called recursively for processing.
    • If the class of Import implements ImportBeanDefinitionRegistrar. It is added to the property of the current configuration class for subsequent processing.
    • If it does not belong to the above two cases, continue to recursively call processConfigurationClass for processing.
  • ImportResource annotation, Bean annotation and default methods on the interface will be parsed and added to the properties of the current configuration class for subsequent processing

Briefly describe several input parameters of the following methods:

  • The two parameters configClass and currentSourceClass directly refer to our main class containing SpringBootApplication annotation.

    configClass indicates who imported the currently processed class, and currentSourceClass indicates the currently processing class. The two are generally the same resource class at the bottom, but there may be recursive calls. At this time, the two may be different.

  • importCandidates is a class imported through the import annotation. Here is autoconfigurationpackages Registrar. Class and autoconfigurationimportselector class

    importCandidates is the class currently being imported, that is, the class being processed here

  • exclusionFilter is defined in ConfigurationClassParser to filter Java lang.annotation. And org springframework. stereotype. Notes at the beginning

  • checkForCircularImports indicates whether to check recursive imports

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
			boolean checkForCircularImports) {
		
		if (importCandidates.isEmpty()) {
			return;
		}
		//Here is error checking to check whether recursion occurs
		if (checkForCircularImports && isChainedImportOnStack(configClass)) {
			this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
		}
		else {
      //First push the current configuration class onto the stack
			this.importStack.push(configClass);
			try {
        //Here, the class imported by the import tag will be processed
				for (SourceClass candidate : importCandidates) {
          
          //AutoConfigurationImportSelector. The class will take the following branch
					if (candidate.isAssignable(ImportSelector.class)) {
						// Candidate class is an ImportSelector -> delegate to it to determine imports
						Class<?> candidateClass = candidate.loadClass();
            //First, create an object of the AutoConfigurationImportSelector class here,
						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) {
            //Here, encapsulate the current configuration class and the object of AutoConfigurationImportSelector into a DeferredImportSelectorHolder object
            //Added to the deferred import collection deferredImportSelectors
							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);
						}
					}
          //AutoConfigurationPackages.Registrar.class will go to this branch
          //In this branch, first create autoconfigurationpackages Object of Registrar
          //Add to the importBeanDefinitionRegistrars property of the current configuration class
					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();
			}
		}
	}

After the above import class is processed, let's go back to doProcessConfigurationClass to see the rest

	@Nullable
	protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {

			......//This part has been analyzed before. Let's continue to look at the following

		// Process any @ImportResource annotations
    // Here is to handle the ImportResource annotation
		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);
			}
		}

		// Process individual @Bean methods
    //Here is the method with Bean annotation inside the configuration class, which is added to the beanMethods attribute of the configuration class
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

		// Process default methods on interfaces
    //If there is Bean annotation on the default method of the interface implemented by the processing configuration class, it will also be added to the beanMethods attribute
		processInterfaces(configClass, sourceClass);

		// Process superclass, if any
    
    //Here to get the parent class of the configuration class. If there is a parent class and the parent class name does not start with java and has not been processed, the parent class will be returned to continue the processing of the parent class.
		if (sourceClass.getMetadata().hasSuperClass()) {
			String superclass = sourceClass.getMetadata().getSuperClassName();
			if (superclass != null && !superclass.startsWith("java") &&
					!this.knownSuperclasses.containsKey(superclass)) {
				this.knownSuperclasses.put(superclass, configClass);
				// Superclass found, return its annotation metadata and recurse
				return sourceClass.getSuperClass();
			}
		}

		// No superclass -> processing is complete
		return null;
	}

Here, the processConfigurationClass method completes the whole analysis.

Now we will come to the last sentence of parse method. Let's go in and have a look

public void parse(Set<BeanDefinitionHolder> configCandidates) {
		......
		//Will go to the following line of code
		this.deferredImportSelectorHandler.process();
	}

Here, we mainly deal with the class of delayed import

		public void process() {
			
			//In the above code, we analyze this There is only one deferredimportselectors
      //DeferredImportSelectorHolder object encapsulated by the objects of the previous configuration class and AutoConfigurationImportSelector class
			List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
			this.deferredImportSelectors = null;
			try {
				if (deferredImports != null) {
					DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
					deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
          //Here, we will group the delayed import classes and add them to the handler. Since we have only one object here, we don't need to pay much attention to this grouping
          //At the same time, the previous configuration class will be added to the configurationClasses property of the handler object
					deferredImports.forEach(handler::register);
          //Next, it will be handed over to the handler for processing
					handler.processGroupImports();
				}
			}
			finally {
				this.deferredImportSelectors = new ArrayList<>();
			}
		}
	}

Let's take a look at how processGroupImports is handled

public void processGroupImports() {
  		//Here we'll deal with it in groups
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				Predicate<String> exclusionFilter = grouping.getCandidateFilter();
        //Here's grouping getImports () goes back to get the configuration class of the system. Let's take a look at this getImports
				grouping.getImports().forEach(entry -> {
				......
			}
		}

Here's grouping Getcandidatefilter () comes from two parts:

  • The other part is the lambda expression defined from the ConfigurationClassParser

This is the method in a static inner class DeferredImportSelectorGrouping of the ConfigurationClassParser class

		public Iterable<Group.Entry> getImports() {
			//There is only one object in deferredImports, which is the DeferredImportSelectorHolder
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
        //Here's this Group is the deferredimport of the previous group getImportSelector(). getImportGroup(); Method
        //Specifically, autoconfigurationimportselector Object of autoconfigurationgroup
        //Let's take a look at the process method first
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
			return this.group.selectImports();
		}

process is in autoconfigurationimportselector Autoconfigurationgroup

		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()));
      //The following line of code is also more important. Let's go in and have a look
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
					.getAutoConfigurationEntry(annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		//Here, we can see how to set up spring boot. The enableautoconfiguration property is used to disable the import of the bean definition of the system configuration
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}

		AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //In the following line, you can see that meta-inf / spring is loaded through ClassLoader Factories file, read the contents. Put into cache
    //At present, I will go to get key = org springframework. boot. autoconfigure. All property configurations of enableautoconfiguration
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
    //Here, get the configuration filter class and create an object to filter the above configurations
    //The configuration filter class here is also obtained from the cache, key = org springframework. boot. autoconfigure. AutoConfigurationImportFilter
		configurations = getConfigurationClassFilter().filter(configurations);
    //This line of code is not critical. We don't have to pay attention to it
		fireAutoConfigurationImportEvents(configurations, exclusions);
    //An AutoConfigurationEntry object is returned here
    //Among them, configurations is the configuration class that the filter can match, and exclusions is empty here
		return new AutoConfigurationEntry(configurations, exclusions);
	}

In the above code, getConfigurationClassFilter() gets:

It's from spring Org. In the factories file springframework. boot. autoconfigure. AutoConfigurationImportFilter

  • org.springframework.boot.autoconfigure.condition.OnClassCondition

    This class mainly checks whether the specified class exists

  • org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

    This class mainly checks whether WebApplicationContext exists

  • org.springframework.boot.autoconfigure.condition.OnBeanCondition

    This class mainly checks whether the specified bean exists

In this process, in the process of generating filter, first read meta-inf / spring autoconfigure metadata through classloader Properties these files.

Here, mainly through the class name ConditionalOnBean, class name ConditionalOnSingleCandidate, class name ConditionalOnClass, class name ConditionalOnWebApplication to filter out inconsistent configuration classes.

The specific algorithm entry is in the match method of the parent class filteringsprinbootcondition of these three classes, and the specific implementation entry is in the getOutcomes method of these three classes respectively.

Since these three classes all implement the Condition interface, these three classes will also be used to filter the configuration class through the Conditional annotation at the beginning of the processConfigurationClass method analyzed above.

It can also be seen from the above that the on-demand loading of springboot is mainly completed by implementing the Condition interface.

Go back to the process method.

		public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
			......//The above code has been analyzed just now
      //Add the AutoConfigurationEntry object returned above to autoConfigurationEntries here
			this.autoConfigurationEntries.add(autoConfigurationEntry);
      
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        //Add the added configuration classes to the entries attribute respectively
        //importClassName is the configuration class found in the new query, and the annotation metadata is the same, which is our main class
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}

In the next selectImports method, these newly added configuration classes will be sorted first, and then assembled into a collection of new Entry(this.entries.get(importClassName), importClassName)) objects.

Here we need to pay attention to this entries. Get (importClassName) is our main class, and importClassName is the configuration class we need to add.

This is mainly to associate the currently imported configuration class with who imported it (here, all configuration classes to be imported are imported by our main class).

When the ConfigurationClass object is created later, the construction method of public ConfigurationClass (metadatareader, metadatareader, @ nullable ConfigurationClass importedby) will be used.

Finally, when adding these configuration classes to beanFactory

Let's go back to the processGroupImports method

	public void processGroupImports() {
			for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
				Predicate<String> exclusionFilter = grouping.getCandidateFilter();
				//The above analysis has found that grouping Getimports () returns a collection of Entry objects
				grouping.getImports().forEach(entry -> {
          //entry.getMetadata() returns our previous main class.
          //The configurationClass here is also our previous main class.
          //This is mainly to set the importedBy property for the configuration classes created in the processImports method
					ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
					try {
            //Here, the processImports method will be called again. This has been analyzed before, but there is a difference here. Let's take a look at the differences
						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);
					}
				});
			}
		}

The parameters of the processImports method are described earlier, so we won't talk about them here

In the following method, importCandidates is a little different from the previous one. The previous one imported through the import annotation will take the first two branches of the for loop respectively, and now it will probably go to the later else branch

	private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
			Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
			boolean checkForCircularImports) {

			this.importStack.push(configClass);
			try {
				for (SourceClass candidate : importCandidates) {
					if (candidate.isAssignable(ImportSelector.class)) {
					......
					}
					else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
					......	
					}
					else {

						// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
						// process it as an @Configuration class
            //Last time I entered this method, I took the above two branches respectively, and now I will probably go to this branch
            //Here, the imported class will be added to the imports attribute. key is the newly imported configuration class, and value is our previous main class
						this.importStack.registerImport(
								currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
            //Here we will deal with the newly added configuration class. Recursion may occur here. Let's analyze the processing logic here
						processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
					}
				}
			}
			......
		}
	}

In the processImports method above, the newly added configuration class will be processed and the processConfigurationClass method will be called.

So far, the processConfigBeanDefinitions method of ConfigurationClassPostProcessor has been analyzed from the part processed by parse.

This part mainly deals with adding all configuration classes to the member variable configurationClasses of the ConfigurationClassParser class through the annotations above the main class. For ImportResource, Bean, etc. on the configuration class, add them to the corresponding properties of the configuration class.

It should be noted that in the whole process, only the configuration classes scanned by ComponentScans will be added to beanFactory.

Let's continue to look at the following code.

	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    ......
		do {
			StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
			parser.parse(candidates);//The previous analysis has come to this point
			parser.validate();

      //Here you will get all the configuration classes
			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
      //alreadyParsed is empty for the first time. Since this method is a do while loop, this variable will be assigned a value later
			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());
			}
      //Here, all the configuration classes obtained earlier will be added to beanFactory
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);
			processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

			candidates.clear();
      //Here is to compare whether the number of beandefinitions in beanFactory has increased before and after. If so, we have added beanFactory in this do while code
      //The following logic is mainly used to judge whether all the currently scanned configuration classes have been added to beanFactory. If there are configuration classes that have not been added today, it will cycle and re execute the above logic
			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());
				}
				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 other codes above are relatively simple. We mainly focus on this reader. loadBeanDefinitions(configClasses); Do a simple analysis.

Method of ConfigurationClassBeanDefinitionReader

	public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
		
		//This class is also used to process the Conditional annotation to determine whether the current configuration class should be filtered out
		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
		for (ConfigurationClass configClass : configurationModel) {
			//Here, each configuration class and its attributes will be processed, encapsulated into beanDefinition and added to beanFactory
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}
	private void loadBeanDefinitionsForConfigurationClass(
			ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
		
    //Here, the Conditional annotation will be judged. If the current class is imported, the class that imports it will be judged
		if (trackedConditionEvaluator.shouldSkip(configClass)) {
			String beanName = configClass.getBeanName();
			if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
				this.registry.removeBeanDefinition(beanName);
			}
			this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
			return;
		}
		//If the class is imported, it will be processed
		if (configClass.isImported()) {
			registerBeanDefinitionForImportedConfigurationClass(configClass);
		}
    //The following is the processing of various attributes of the configuration class
    //bean annotations on processing methods
		for (BeanMethod beanMethod : configClass.getBeanMethods()) {
			loadBeanDefinitionsForBeanMethod(beanMethod);
		}
		//Processing imported resources
		loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    //Process imported ImportBeanDefinitionRegistrar
		loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
	}

You can also see from the above code that a simple configuration class, if configclass If isimported() returns false, it will not be added to beanFactory. That is, if the configuration class is not imported, the configuration class will not be added to beanFactory.

As mentioned earlier, the classes scanned by ComponentScans are added to beanFactory during processing, and other configuration classes are added in the above methods.

All added classes can be roughly divided into two parts:

  1. Through the annotation on the class, it is directly added to the configuration class. The imported class of these configuration classes is the current main class.
  2. The other part is to read meta-inf / spring through the @ Import(AutoConfigurationImportSelector.class) annotation on the main class Factories file, via meta-inf / spring-autoconfigure-metadata The class to be processed after filtering the properties file.

The above two parts will be processed recursively, layer by layer. Moreover, all processing processes will also be filtered according to the Conditional annotation.

At the same time, it should also be noted that although all added to beanFactory are beanD, the details are different. For example:

The ScannedGenericBeanDefinition is added through the ComponentScans annotation

ConfigurationClassBeanDefinition is added by the bean annotation on the processing method

AnnotatedGenericBeanDefinition is another common configuration class

Up to the above, the whole analysis is over.

There are many recursive calls involved in the whole process. In order not to make the article too scattered, many details have been omitted in the above analysis process.

Due to personal ability problems, the above analysis may have errors or unclear descriptions. Please comment and correct.

Topics: Java Spring Spring Boot