Springboot source code analysis first bullet - automatic assembly implementation

Spring boot doesn't need much. It's an artifact that liberates both hands of Java development.
The most remarkable feature is de configuration, automatic assembly and automatic configuration. Let developers only focus on business development
Today, let's learn how to realize the source code of automatic assembly

Prepare in advance

  • Direct reference to springboot package 2.5.6
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.6</version>
</parent>
  • Post the web of the previous MVC scanning package XML configuration
<!-- commonly applicationContext There will be scanning package path configuration -->
<context:component-scan base-package="com.demo.xxx"></context:component-scan>

In the past, there was an entry to specify the scanning path, but springboot did not have this configuration. It can scan out how it is implemented. Let's find out!

Process Overview

  1. The entry must be the run method, and the parameters passed in are the current startup class
  2. Refresh the class in the container before it is initialized, and then refresh the class in the container
  3. After the container registration phase is completed, all classes that need to be loaded are registered in the BeanDefinitionMap
  4. Then, calling the processor callback of the registered bean in context, parsing bean in the springboot package.
  5. The focus is to register a class and get the scanning path when parsing the class, that is, the current path ', This shows that the springboot project scans the startup class layer by default, scans our handwritten code and registers it in the container, but the required dependencies still do not want to be registered in the container
  6. There is @ import annotation on the startup class, which will be processed together during parsing, and then get the callback of the imported class to get the class that needs to be assembled automatically
  7. Resolve the classes that need to be loaded automatically in the callback class by reading meta-inf / spring.exe in the file directory in the package Get the value of the corresponding key from the configuration of factories
  8. Finally, if you want to register the container, the automatic assembly process is over

Source code analysis

The above process can be divided into three steps, step by step analysis.

1. Register the startup class with the container

  • The entry is the run() method, which goes directly to the source code
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args){
	//primarySource startup class
	return run(new Class<?>[] { primarySource }, args);
}
  • Next, run goes down to spring application #run()
public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	DefaultBootstrapContext bootstrapContext = createBootstrapContext();
	ConfigurableApplicationContext context = null;
	configureHeadlessProperty();
	//Get the listener that needs to be registered
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting(bootstrapContext, this.mainApplicationClass);
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		//Environment and configuration will be discussed later
		ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
		configureIgnoreBeanInfo(environment);
		//Print start banner
		Banner printedBanner = printBanner(environment);
		//Create web container
		context = createApplicationContext();
		//Set the startup class DefaultApplicationStartup of the web container
		context.setApplicationStartup(this.applicationStartup);
		//Make some preparations before container initialization #### let's look at this first
		prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
		//Initial container
		refreshContext(context);
		//Initialization complete
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}
		//Notification of container creation completion
		listeners.started(context);
		//Callback callrunners implements the callback of the CommandLineRunner interface
		callRunners(context, applicationArguments);
	}catch (Throwable ex) {
		handleRunFailure(context, ex, listeners);
		throw new IllegalStateException(ex);
	}
	try {
		//Notification that the container has started
		listeners.running(context);
	}catch (Throwable ex) {
		handleRunFailure(context, ex, null);
		throw new IllegalStateException(ex);
	}
	return context;
}
  • Let's look at the processing before loading the container
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
	SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
   	//Some settings of container
	context.setEnvironment(environment);
	postProcessApplicationContext(context);
	applyInitializers(context);
	listeners.contextPrepared(context);
	if (this.logStartupInfo) {
		logStartupInfo(context.getParent() == null);
		logStartupProfileInfo(context);
	}
	// Add boot specific singleton beans
   	//Register some single column bean s with the container
	ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
	if (printedBanner != null) {
		beanFactory.registerSingleton("springBootBanner", printedBanner);
	}
	if (beanFactory instanceof DefaultListableBeanFactory) {
		((DefaultListableBeanFactory) beanFactory)
				.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
	}
	if (this.lazyInitialization) {
		context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
	}
	// Load the sources
   	//This is the key point. What is returned in getAllSources() is XXX passed in the run method Class is actually the startup class
	Set<Object> sources = getAllSources();
	Assert.notEmpty(sources, "Sources must not be empty");
   	//From the above, we know that the sources passed in here is actually the startup class. Next, let's see how to load here###
	load(context, sources.toArray(new Object[0]));
	listeners.contextLoaded(context);
}
  • What you get here is the startup class, and what you load is the class, all the way down to beandefinitionloader #load (class <? > source)
private void load(Class<?> source) {
	if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
		// Any GroovyLoaders added in beans{} DSL can contribute beans here
		GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
		((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
	}
	//return !(type.isAnonymousClass() || isGroovyClosure(type) || hasNoConstructors(type));
	//If the class is not empty, is not a Groovy closure, or is not an anonymous class, register directly with the container
	if (isEligible(source)) {
		//That is to say, first
		this.annotatedReader.register(source);
	}
}

//this. annotatedReader. The register (source) will then be registered in the IOC container
AnnotatedBeanDefinitionReader
-> registerBean(componentClass)
-> doRegisterBean
-> BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);

The first point here is to complete. The original intention is to register the startup class in the container, and then use this class in the context callback to complete the scanning of business code, because there is no place to configure which classes under package names spring can scan, similar to completing web Component scan configuration in XML

2. Register the business class with the container

  • Next, go back to the original spring application #run and find the following code
//Before container refresh
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//Focus on this refresh container###
refreshContext(context);
//All the way down here
protected void refresh(ApplicationContext applicationContext) {
	Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
	((AbstractApplicationContext) applicationContext).refresh();
}
//Next, go to abstractapplicationcontext Refresh() IOC name
//Don't say much. Just look at this
// Invoke factory processors registered as beans in the context.
// In the context, calling the factory processor registered as bean is callback.
invokeBeanFactoryPostProcessors(beanFactory);

//Down here
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
  • Next, go to PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(), and paste only the key code
public static void invokeBeanFactoryPostProcessors(
	ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {

	// Invoke BeanDefinitionRegistryPostProcessors first, if any.
	Set<String> processedBeans = new HashSet<>();

	if (beanFactory instanceof BeanDefinitionRegistry) {
		 //Omit some codes
           ......
		// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
        //Call implementation class   
        //Also note that the breakpoint debugging spring package implements only one of this class 					
        //Configuration class postprocessor  
		String[] postProcessorNames =
		beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
		for (String ppName : postProcessorNames) {
			if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                 //Add to list
				currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
				processedBeans.add(ppName);
			}
		}
		sortPostProcessors(currentRegistryProcessors, beanFactory);
		registryProcessors.addAll(currentRegistryProcessors);
        //Callback for the registered bean. Next, see here###
		invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
		
	//Omit some codes
	......
}

//invokeBeanDefinitionRegistryPostProcessors
private static void invokeBeanDefinitionRegistryPostProcessors(
		Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {

	for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
		StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
				.tag("postProcessor", postProcessor::toString);
		//Start to callback the bean that implements the BeanDefinitionRegistryPostProcessor interface		
		postProcessor.postProcessBeanDefinitionRegistry(registry);
		postProcessBeanDefRegistry.end();
	}
}
  • After breakpoint debugging, it will go to spring-context-5.3.12 ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry under jar package
  • One thing to note is that if the startup class is registered manually, there must be a startup class in candidate names. We ignore other classes and mainly focus on the scanning and loading of startup classes
//All the way down here
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
	String[] candidateNames = registry.getBeanDefinitionNames();

	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);
			}
		}
        //Determine whether the @ Configuration annotation is added
		else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            //If it is satisfied, it will be added to the list. The startup class here must have added this annotation and can be verified by itself
			configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
		}
	}
		
    	//Omit some codes
        ......
	// 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 {
        //Start parsing the @ Configuration annotated class #### and look at this first
		parser.parse(candidates);
		parser.validate();
		//Get the class that needs to be loaded after parsing
		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());
		}
		//Register the class to be loaded into the container
		this.reader.loadBeanDefinitions(configClasses);
		alreadyParsed.addAll(configClasses);

	//Omit some codes
        ......
}
  • Take a look at parser parse(candidates); What did you do? Go to ConfigurationClassParser#parse()
ublic void parse(Set<BeanDefinitionHolder> configCandidates) {
	for (BeanDefinitionHolder holder : configCandidates) {
		BeanDefinition bd = holder.getBeanDefinition();
		try {
               //The startup class annotates @ Configuration, which belongs to annotation definition
			if (bd instanceof AnnotatedBeanDefinition) {
                //Next, go here##
				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);
		}
	}
	//Analyze the process after parse
	this.deferredImportSelectorHandler.process();
}
  • Down to the real working class, ConfigurationClassParser#doProcessConfigurationClass parses the core class
protected final SourceClass doProcessConfigurationClass(
	ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
	throws IOException {
	//Parse @ Component annotation
	if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
		// Recursively process any member (nested) classes first
		processMemberClasses(configClass, sourceClass, filter);
	}

	//@PropertySource annotation
	// Process any @PropertySource annotations
	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");
		}
	}

	//@ComponentScan annotation scans the corresponding package path
	//@This annotation is available on the SpringBootApplication, or you can specify it yourself
	// 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
			//Find out if the ComponentScan annotation uses value
			//Next, let's see how to scan####
			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());
				}
			}
		}
	}
	//@After the Import annotation is analyzed and scanned, the next step is to focus on here###
	// Process any @Import annotations
	processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
	//@ImportResource annotation
	// Process any @ImportResource annotations
	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);
		}
	}
	//@Bean annotation method
	// Process individual @Bean methods
	Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
	for (MethodMetadata methodMetadata : beanMethods) {
		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") &&
				!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;
}
  • @ComponentScan specifies the scan package path, ConfigurationClassParser#doProcessConfigurationClass
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
-> ComponentScanAnnotationParser.parse

//To this method
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
	//Omit some codes
	......

	Set<String> basePackages = new LinkedHashSet<>();
	//Get the manually written scan package path, which is the package path written in the annotation
	String[] basePackagesArray = componentScan.getStringArray("basePackages");
	for (String pkg : basePackagesArray) {
		String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
		Collections.addAll(basePackages, tokenized);
	}
	for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
		basePackages.add(ClassUtils.getPackageName(clazz));
	}
	//We didn't write the scan path manually here, so we will go here
	if (basePackages.isEmpty()) {
		//Next, look here
		//public static String getPackageName(String fqClassName) {
           //Assert.notNull(fqClassName, "Class name must not be null");
           //int lastDotIndex = fqClassName.lastIndexOf(PACKAGE_SEPARATOR);
           //return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : "");
           //}
		// Then found the private static final char PACKAGE_SEPARATOR = '.';
		//What you get is the package name path of the startup class, which also verifies that the springboot project scans the startup class layer by default
		basePackages.add(ClassUtils.getPackageName(declaringClass));
	}

	scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
		@Override
		protected boolean matchClassName(String className) {
			return declaringClass.equals(className);
		}
	});
	//Scan the class files under the specified package path. Next, I'll continue to look down###
	return scanner.doScan(StringUtils.toStringArray(basePackages));
}
  • Next, go to ClassPathBeanDefinitionScanner#doScan()
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
	Assert.notEmpty(basePackages, "At least one base package must be specified");
	Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
	for (String basePackage : basePackages) {
		//Look down and find here###
		Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
		for (BeanDefinition candidate : candidates) {
			ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
			candidate.setScope(scopeMetadata.getScopeName());
			String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
			if (candidate instanceof AbstractBeanDefinition) {
				//Callback because the container has handled the callback step, the newly registered bean needs to callback itself
				postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
			}
			if (candidate instanceof AnnotatedBeanDefinition) {
				AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
			}
			if (checkCandidate(beanName, candidate)) {
				BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
				definitionHolder =
						AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
				beanDefinitions.add(definitionHolder);
				//Register with the container. I won't say much here
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}
	}
	return beanDefinitions;
}
  • Next, go to classpathscanningcandidatecomponentprovider #findcandidatecomponents - > scancandidatecomponents
//This is the familiar reading stream
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
	Set<BeanDefinition> candidates = new LinkedHashSet<>();
	try {
		//resourcePattern = **/*. Class suffix
		String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
				resolveBasePackage(basePackage) + '/' + this.resourcePattern;
		//Read stream		
		Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
		boolean traceEnabled = logger.isTraceEnabled();
		boolean debugEnabled = logger.isDebugEnabled();
		for (Resource resource : resources) {
			if (traceEnabled) {
				logger.trace("Scanning " + resource);
			}
			try {
				MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
				//In fact, all annotations that need to be loaded implement @ Component
				if (isCandidateComponent(metadataReader)) {
					ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
					sbd.setSource(resource);
					if (isCandidateComponent(sbd)) {
						if (debugEnabled) {
							logger.debug("Identified candidate component class: " + resource);
						}
						//Scan the class file under the corresponding path into the list, and then register it with the container
						candidates.add(sbd);
					}
					.... //Omit some codes
	return candidates;
}

So far, we have registered our own business code into the container, but the bean s that the springboot framework needs to rely on are still not registered. Next, let's see how to implement automatic assembly

3. Register the auto assembled classes that you need to rely on with the container

  • Let's go back to configurationclassparser In the doprocessconfigurationclass method
//Find this line to parse the @ import annotation
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

//Next, go to the processImports method. Only those annotated with @ Import will be included
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);
		}
         //Only the implementation of DeferredImportSelector can go here
         //Therefore, find the annotation on the startup class, which is only introduced in @ EnableAutoConfiguration
         //@Import(AutoConfigurationImportSelector.class)
         //DeferredImportSelector inherits ImportSelector
		if (selector instanceof DeferredImportSelector) {
            //Next, go here###
			this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
		}
		else {
			String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
			Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
			processImports(configClass, currentSourceClass, importSourceClasses, false);
		}
	}
	...//Omit some codes
	
}

  • Next, go to the ConfigurationClassParser#handler method and the internal class DeferredImportSelectorHandler
//Post the two methods of this class
private class DeferredImportSelectorHandler {

	@Nullable
	private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();

	public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
		DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(configClass, importSelector);
		//The use of null local variable lock is only in process, that is, in process
		if (this.deferredImportSelectors == null) {
			DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
			handler.register(holder);
			//recursion
			handler.processGroupImports();
		}
		else {
			//Normally, it will not be null and added to this list
			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);
				//Callback according to the added ImportSelector class. Here's the next step###
				handler.processGroupImports();
			}
		}
		finally {
			this.deferredImportSelectors = new ArrayList<>();
		}
	}
}

The above analysis has obtained all the class collections introduced with @ Import and implemented DeferredImportSelector

  • Return to configurationclassparser #parse again (set < beandefinitionholder > configcandidates)
//Find the last line of code for the method
this.deferredImportSelectorHandler.process();
//Go to the internal class DeferredImportSelectorHandler#process above, and then go to this line of code
handler.processGroupImports();

public void processGroupImports() {
	for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
		Predicate<String> exclusionFilter = grouping.getCandidateFilter();
		//First look at getImports()###
		grouping.getImports().forEach(entry -> {
			ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
			try {
				//Here is another way to handle the import annotation, but the judgment is different
				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);
			}
		});
	}
}
  • ConfigurationClassParser#getImports()
public Iterable<Group.Entry> getImports() {
	for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
		//Get the class that needs to be loaded automatically###		
		this.group.process(deferredImport.getConfigurationClass().getMetadata(),
		deferredImport.getImportSelector());
	}
	//After processing, return the class of the required automatic device
	return this.group.selectImports();
}
  • This group will be connected to the internal class AutoConfigurationGroup#process of AutoConfigurationImportSelector
@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()));
	//Resolve the classes that need to be automatically assembled. Next, let's see how to find the classes that need to be resolved### 				
	AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
			.getAutoConfigurationEntry(annotationMetadata);
	//Save it in the following selectImports and return it after processing		
	this.autoConfigurationEntries.add(autoConfigurationEntry);
	for (String importClassName : autoConfigurationEntry.getConfigurations()) {
		this.entries.putIfAbsent(importClassName, annotationMetadata);
	}
}
	
	//Get the follow-up of the automatically assembled class and don't post it later
	@Override
	public Iterable<Entry> selectImports() {
		if (this.autoConfigurationEntries.isEmpty()) {
			return Collections.emptyList();
		}
		//classname to exclude
		Set<String> allExclusions = this.autoConfigurationEntries.stream()
				.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
	    //list collection of classname s that need to be automatically assembled
		Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
				.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
				.collect(Collectors.toCollection(LinkedHashSet::new));
		//remove		
		processedConfigurations.removeAll(allExclusions);
		//Sort and encapsulate
		return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
				.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
				.collect(Collectors.toList());
	}
}
//getgetAutoConfigurationEntry -> getCandidateConfigurations
//To AutoConfigurationImportSelector
//Obtain the class to be automatically assembled for the specified key through classLoader
List<String> configurations = 
//getSpringFactoriesLoaderFactoryClass return EnableAutoConfiguration.class;
//The key here is the class path of auto assembly EnableAutoConfiguration
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
		getBeanClassLoader());	


public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
	ClassLoader classLoaderToUse = classLoader;
	if (classLoaderToUse == null) {
		classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
	}
	//The key is the EnableAutoConfiguration class path
	//org.springframework.boot.autoconfigure.EnableAutoConfiguration
	String factoryTypeName = factoryType.getName();
	//Here, get the contents of the automatic assembly of the corresponding class loader from the cache, and then take out the value of the key, which returns an empty list by default
	return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

  • Next, spring factoriesloader #loadspring factories, get all classes that need automatic devices and store them in the cache
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
	//Get from cache first
	Map<String, List<String>> result = cache.get(classLoader);
	if (result != null) {
		return result;
	}

	result = new HashMap<>();
	try {
		//String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
        //From the path, we know that it should be read from the specified file. Next, let's look at the corresponding file
		Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			UrlResource resource = new UrlResource(url);
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			for (Map.Entry<?, ?> entry : properties.entrySet()) {
				String factoryTypeName = ((String) entry.getKey()).trim();
				String[] factoryImplementationNames =
						StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
				for (String factoryImplementationName : factoryImplementationNames) {
					result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
							.add(factoryImplementationName.trim());
				}
			}
		}

		// Replace all lists with unmodifiable lists containing unique elements
		result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
				.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
		//The map key saved to the corresponding class loader is the corresponding class loader		
		cache.put(classLoader, result);
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load factories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
	return result;
}
  • Only the springboot package is cited here, so look for meta-inf / spring.exe in the spring boot autoconfigure package directory Find the corresponding key in the factories file
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
......
  • After corresponding to the classes that need to be assembled automatically, go back to ConfigurationClassParser#processGroupImports
public void processGroupImports() {
	for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
		Predicate<String> exclusionFilter = grouping.getCandidateFilter();
		grouping.getImports().forEach(entry -> {
			ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
			try {
				//We will continue to scan and load classes recursively###
				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);
			}
		});
	}
}
  • Next, go back to ConfigurationClassParser#processImports, because it is no longer an ImportSelector at this time. Find the else
else {
	// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
	// process it as an @Configuration class
	//Is not the direction of the implementation class of ImportSelector
	this.importStack.registerImport(
			currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
	//Load into local Map store	
	processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
//processConfigurationClass last
//Store to local map					
this.configurationClasses.put(configClass, configClass);	

The classes that need to be automatically assembled already exist in the local map, and the next step should be to register in the late container

  • Back to ConfigurationClassPostProcessor#processConfigBeanDefinitions
  • The previous code is actually analyzing parser parse(candidates);, Next, go to the back line
parser.parse(candidates);
parser.validate();
//public Set<ConfigurationClass> getConfigurationClasses() {
//		return this.configurationClasses.keySet();
//	}
//Retrieve and store the class to be registered return this configurationClasses. keySet();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);

// Take out the reader and create it without
// 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());
}
//Register with container
this.reader.loadBeanDefinitions(configClasses);

Here, the whole automatic assembly process is over. The source code is relatively flexible, so you need to be patient
That is the whole content of this chapter.

Previous: The sixth bullet of SpringMvc source code analysis - handwritten high imitation version based on SpringMvc source code
Next: Springboot source code analysis second bullet - automatic configuration implementation

The east corner is dead, sang Yu is not late

Keywords: Java Spring Spring Boot architecture

Added by dmphotography on Sat, 15 Jan 2022 10:21:18 +0200