From: juejin. Cn/post / 684490…

I analyzed the springBoot startup process in my last blog post, and the broad Outlines are just the tip of the iceberg. Today, let’s take a look at springBoot’s highlight feature: automated assembly.

Let’s start with @SpringBootApplication.

In the startup process section, we describe the general startup steps of SpringBoot2 and explain the source code in detail. But it’s not expanded in the refresh container, refreshContext(context); A single line of code with too much going on behind the scenes. So in order not to be too distracting, this article will try to use methods related to annotation @SpringBootApplication.

1) springBoot starts class loading

Load (context, sources.toarray (new Object[0])); load(context, sources.toarray (new Object[0])); Following this method will eventually execute BeanDefinitionLoader’s load method:

	private int load(Object source) {
		Assert.notNull(source."Source must not be null"); // If it is class, enable annotation typeif (sourceinstanceof Class<? >) {returnload((Class<? >)source); } // Enable XML parsing for resource typeif (source instanceof Resource) {
			return load((Resource) source); } // Enable package scanning for package type, for example: @ComponentScanif (source instanceof Package) {
			return load((Package) source); } // If it is a string, load it directlyif (source instanceof CharSequence) {
			return load((CharSequence) source);
		}
		throw new IllegalArgumentException("Invalid source type "+ source.getClass()); } Duplicate codeCopy the code

Load (Class
source)

The above method determines whether the launcher class contains the @Component annotation, which our launcher class does not. Further down, AnnotationUtils determines whether the annotation is included or not by recursion, and annotations on annotations that contain a specified type are fine. The startup class contains the @SpringBootApplication annotation, which leads to the @SpringBootConfiguration annotation, which leads to the @Component annotation, and which leads to the @Component annotation:

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { } Copy the codeCopy the code

After finding the @Component annotation, the object is identified as a Spring bean, and its information is wrapped as beanDefinitaion, which is added to the container’s beanDefinitionMap. As follows:

In this way, our launcher class is wrapped as
AnnotatedGenericBeanDefinitionAll subsequent startup class processing is based on this object.

2) Entrance of automatic assembly:

Start by refreshing the container:
public void refresh() throws BeansException, IllegalStateException { //... invokeBeanFactoryPostProcessors(beanFactory); / /... } Duplicate codeCopy the code

Eliminating the irrelevant code above, continue to follow up invokeBeanFactoryPostProcessors method:

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { / / start spring beanFactoryPostProcessor corresponding implementation classes PostProcessorRegistrationDelegate. InvokeBeanFactoryPostProcessors (the beanFactory, getBeanFactoryPostProcessors()); // Detect a LoadTimeWeaver and preparefor weaving, if found in the meantime
		// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
		if(beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); }} Copy the codeCopy the code

The beanFactoryPostProcessor interface is an extension interface to Spring and, as the name suggests, beanFactory. This interface can be used to modify bean metadata information before refreshing the container. The implementation of the implementation, we continue to follow the above implementation logic. Continue to follow up the above invokeBeanFactoryPostProcessors method, the first line is the key:

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors()); Copy the codeCopy the code

One is the core of the proxy class appeared, tasks entrusted to perform post processors AbstractApplicationContext utility class. What tasks do you delegate when you start a project?

You may remember applyInitializers(Context) in your first blog post about SpringApplication classes; Method, it will be three default internal classes added to the spring container DefaultListableBeanFactory, as follows:

/ / set the configuration warning ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor
SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
ConfigFileApplicationListener$PropertySourceOrderingPostProcessorCopy the codeCopy the code

Look at the specific task execution details, follow-up invokeBeanFactoryPostProcessors method:

if(beanFactory instanceof BeanDefinitionRegistry) { BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory; List<BeanFactoryPostProcessor> regularPostProcessors = new LinkedList<>(); List<BeanDefinitionRegistryPostProcessor> registryProcessors = new LinkedList<>(); / / here began to traverse the above three inner classes, if belong to BeanDefinitionRegistryPostProcessor subclasses, / / added to the bean registered collection, or join the regularPostProcessors, there are laws set can be seen from the name.for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
				if(postProcessor instanceof BeanDefinitionRegistryPostProcessor) { BeanDefinitionRegistryPostProcessor registryProcessor =  (BeanDefinitionRegistryPostProcessor) postProcessor; registryProcessor.postProcessBeanDefinitionRegistry(registry); registryProcessors.add(registryProcessor); }else {
					regularPostProcessors.add(postProcessor);
				}
			}

			// Do not initialize FactoryBeans here: We need to leave all regular beans
			// uninitialized to let the bean factory post-processors apply to them!
			// Separate between BeanDefinitionRegistryPostProcessors that implement
			// PriorityOrdered, Ordered, and the rest.
			List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();

			// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
			String[] postProcessorNames =
					beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true.false); / / executed first type for PriorityOrdered BeanDefinitionRegistryPostProcessor / / PriorityOrdered types shows that as a priorityfor (String ppName : postProcessorNames) {
				if(beanFactory.isTypeMatch(ppName, PriorityOrdered. Class)) {/ / get the corresponding bean currentRegistryProcessors. Add (the beanFactory) getBean (ppName, BeanDefinitionRegistryPostProcessor.class)); / / used to store has carried out ` BeanDefinitionRegistryPostProcessor ` processedBeans. Add (ppName); } } sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear(); // Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered. postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class,true.false); / / second execution type for Ordered BeanDefinitionRegistryPostProcessor / / Ordered that execute sequentiallyfor (String ppName : postProcessorNames) {
				if(! processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) { currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));  processedBeans.add(ppName); } } sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear(); // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear. boolean reiterate =true; / / loop execution type not to PriorityOrdered, Ordered BeanDefinitionRegistryPostProcessor typewhile (reiterate) {
				reiterate = false;
				postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true.false);
				for (String ppName : postProcessorNames) {
					if(! processedBeans.contains(ppName)) { currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); reiterate =true; } } sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear(); } Now, invoke the postProcessBeanFactory callback of all processors handled so far. Priority registration processing class invokeBeanFactoryPostProcessors (registryProcessors, the beanFactory); / / execution have rule processing class invokeBeanFactoryPostProcessors (regularPostProcessors, the beanFactory); } Duplicate codeCopy the code

Take a look at the core code:

String[] postProcessorNames =beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true.false); Copy the codeCopy the code

This line of code by type BeanDefinitionRegistryPostProcessor for dealing with the class name for: “org.springframework.context.annotation.internalConfigurationAnnotationProcessor” And could not be found in the source internalConfigurationAnnotationProcessor class, why? The initial look at this code is really confused for a long time. In the first blog post, when start springBoot, create springBoot container context AnnotationConfigEmbeddedWebApplicationContext, will assemble a few default bean:

	public AnnotationConfigEmbeddedWebApplicationContext() {/ / assembly here enclosing reader = new AnnotatedBeanDefinitionReader (this); this.scanner = new ClassPathBeanDefinitionScanner(this); } Duplicate codeCopy the code

Continue to follow up will perform registerAnnotationConfigProcessors method:

public static final String CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME =
			"org.springframework.context.annotation.internalConfigurationAnnotationProcessor"; / / will packaged into RootBeanDefinition internalConfigurationAnnotationProcessor corresponding to the class is loaded into the containerif(! registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); } Duplicate codeCopy the code

Here, the answer is clear. InternalConfigurationAnnotationProcessor as the bean name, real class is ConfigurationClassPostProcessor container.

Continue to process behind, after obtaining ConfigurationClassPostProcessor, began to perform BeanDefinitionRegistryPostProcessor:

/ / perform assembly logic invokeBeanDefinitionRegistryPostProcessors (priorityOrderedPostProcessors, registry); Copy the codeCopy the code

3) Start automatic configuration logic (start the configuration specified by the class, not the default configuration) :

First get ConfigurationClassParser, which is the core parser class for all configuration classes. All parse logic is in parser. Parse (candidates); In, let’s look at it in detail:

	public void parse(Set<BeanDefinitionHolder> configCandidates) {
		this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();

		for(BeanDefinitionHolder holder : configCandidates) { BeanDefinition bd = holder.getBeanDefinition(); Try {// is an annotation classif (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 (Exception ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex); }} / / perform configuration class processDeferredImportSelectors (); } Duplicate codeCopy the code

Follow up on the parse method:

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException { //... // Shrink shrinkage process the configuration class and its superclass hierarchy. SourceClasssourceClass = asSourceClass(configClass);
		do{// Loop through the bean and, if it has a parent class, the parent class. Until the end.sourceClass = doProcessConfigurationClass(configClass, sourceClass);
		}
		while (sourceClass ! = null); this.configurationClasses.put(configClass, configClass); } Duplicate codeCopy the code

Continue to follow up doProcessConfigurationClass method, this method can be said to be the core of the spring framework supports annotation configuration logic, to look at:

 protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceThrows IOException {Class) throws IOException {// Handle inner Class logic. processMemberClasses(configClass,sourceClass); // Process any @propertysource Annotations // Parse for attribute configurationfor (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
                sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) {
            if (this.environment instanceof ConfigurableEnvironment) {
                processPropertySource(propertySource);
            }
            else {
                logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                        "]. Reason: Environment must implement ConfigurableEnvironment"); }} / / this is according to start the class @ ComponentScan annotations to scan the project bean AnnotationAttributes ComponentScan = AnnotationConfigUtils. AttributesFor (sourceClass.getMetadata(), ComponentScan.class);
        if(componentScan ! = null && ! this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            // 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 ifNecessary // Iterate over the beans in our project, parsing further if they are defined by annotationsforBeanDefinitionHolder holder: scannedBeanDefinitions) {// Determine if this is an annotation beanif(ConfigurationClassUtils.checkConfigurationClassCandidate(holder.getBeanDefinition(), Enclosing metadataReaderFactory)) {/ / here is the key, recursive parsing. Parse (holder.getBeanDefinition().getBeanclassName (), holder.getBeanname ())); }}} // Process any @import Annotations // In many cases, imported configuration classes also contain imported class annotations. processImports(configClass,sourceClass, getImports(sourceClass), true); // Process any @importResource Annotations // Parse the imported XML configuration classif (sourceClass.getMetadata().isAnnotated(ImportResource.class.getName())) {
            AnnotationAttributes importResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
            String[] resources = importResource.getAliasedStringArray("locations", ImportResource.class, sourceClass);
            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
        Set<MethodMetadata> beanMethods = sourceClass.getMetadata().getAnnotatedMethods(Bean.class.getName());
        for(MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Get the default method in the interface, 1.8 + processing logicfor (SourceClass ifc : sourceClass.getInterfaces()) {
            beanMethods = ifc.getMetadata().getAnnotatedMethods(Bean.class.getName());
            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)); } } } // Process superclass,ifAny // Returns if the class has a parent class. If the upper method is not empty, the recursive execution continues.if (sourceClass.getMetadata().hasSuperClass()) {
            String superclass = sourceClass.getMetadata().getSuperClassName();
            if(! 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 completereturnnull; } Duplicate codeCopy the code

Take a look at the logic for getting the imported configuration class:

processImports(configClass, sourceClass, getImports(sourceClass), true); Copy the codeCopy the code

Follow up getImports method:

You can see that my custom bean is loaded as an import. In addition
processImportsMethod executes logic and above
parseI’m going to do the same thing, I’m going to do it recursively, so I’m not going to do the expansion here.

4) Start executing the default SpringBoot configuration logic

Continue back to the Parse method in ConfigurationClassParser, returning to the last step of the method:

public void parse(Set<BeanDefinitionHolder> configCandidates) { //... / / start the default configuration processDeferredImportSelectors (); } Duplicate codeCopy the code

Continue to follow up this method processDeferredImportSelectors:

private void processDeferredImportSelectors() {
		List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
		this.deferredImportSelectors = null;
		Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);

		for(DeferredImportSelectorHolder deferredImport : deferredImports) { ConfigurationClass configClass = deferredImport.getConfigurationClass(); Try {/ / access configuration class String [] imports. = deferredImport getImportSelector () selectImports (configClass. For getMetadata ()); ProcessImports (configClass, asSourceClass(configClass), asSourceClasses(imports))false);
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" +
						configClass.getMetadata().getClassName() + "]", ex); }}} copy the codeCopy the code

GetImportSelector () method to obtain the object selector for EnableAutoConfigurationImportSelector, continue to follow the object selectImports method:

@Override public String[] selectImports(AnnotationMetadata metadata) { try { AnnotationAttributes attributes = getAttributes(metadata); / / get the default configuration class List < String > configurations = getCandidateConfigurations (metadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(metadata, attributes); configurations.removeAll(exclusions); configurations = sort(configurations); recordWithConditionEvaluationReport(configurations, exclusions);returnconfigurations.toArray(new String[configurations.size()]); } catch (IOException ex) { throw new IllegalStateException(ex); }} Copy the codeCopy the code

The way to do this, as described in detail in the previous blog, is to get the specified class in Spring. factories using the class type EnableAutoConfiguration

protected Class<? >getSpringFactoriesLoaderFactoryClass() {
		returnEnableAutoConfiguration.class; } Duplicate codeCopy the code

SpringBoot provides us with the following configuration classes, about 100 more:

After obtaining the configuration provided by springBoot, the processImports method is called again for recursive resolution, and the selective configuration is carried out according to our customized configuration file.

With so many configuration classes, it is impossible to load them all and the project will not use them. What are the rules of choice?

Full details will follow in a later blog post.

SpringBoot automatic assembly process is introduced here.