The main content of the passage

  1. Spring Boot automatic assembly principle [case simulation]
  2. Spring Boot automatic assembly source code analysis

Principle of automatic loading in SpringBoot

SpringBoot is out of the box and does a lot of things for us.

Realization of automatic assembly

This is mainly automated assembly through the ImportSelector interface

ImportSelector

The ImportSelector interface is the core interface for Spring to import external configurations and plays a decisive role in SpringBoot’s automated configuration and @enablexxx (functional annotations). When an ImportSelector implementation Class is introduced using @import on an @con figuration annotated Class, the Class names returned in the implementation Class are defined as beans.

package org.springframework.context.annotation;

import org.springframework.core.type.AnnotationMetadata;

public interface ImportSelector {

	/**
	 * Select and return the names of whichclass(es) should be imported based on * the {@link AnnotationMetadata} of the importing @{@link Configuration} class. */  String[] selectImports(AnnotationMetadata importingClassMetadata); }Copy the code

An inheritance diagram of the interface



DeferredImportSelector

The DeferredImportSelector interface inherits from the ImportSelector interface. The difference between the DeferredImportSelector interface and the ImportSelector interface is when the bean is loaded. DeferredImportSelector needs to wait until all @con figuration is executed before loading.

Spring Boot automatic assembly case

Project case portal: autoConfig

Case Main code

  1. Create a Configuration class for the production Bean, but we do not declare it using the @Configuration annotation.

public class MyConfig {
    @Bean(value = "chenfu", name = "chenfu")
    public Map<Object, Object> getMap() {
        HashMap<Object, Object> map = new HashMap<>();
        map.put("code", 200);
        map.put("msg"."success");
        String nowDate = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        map.put("data", nowDate);
        returnmap; }}Copy the code


public class MyConfigImportSelector implements ImportSelector { @Override public String[] SelectImports (AnnotationMetadata AnnotationMetadata) {// Returns the configuration namereturnnew String[]{MyConfig.class.getName()}; }}Copy the code

@SpringBootApplication
@Import(value = MyConfigImportSelector.class)
public class AutoConfigApp {
    public static void main(String[] args) {
        ConfigurableApplicationContext app = SpringApplication.run(AutoConfigApp.class, args);
        Object chenfu = app.getBean("chenfu"); System.out.println(chenfu); }}Copy the code

The case shows that the Spring Boot auto-assembly object does not use the Spring object to create annotation declarations (@controller, @Service, @repostiroty). Instead, it dynamically loads the bean programmatically. The parsing of these objects in Spring Boor is done primarily in the processImports method of the ConfigurationClassParser class

Parsing of ImportSelector

ConfigurationClassParser

ConfigurationClassParser class

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
		Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
	if (importCandidates.isEmpty()) {
		return;
	}
	if (checkForCircularImports && isChainedImportOnStack(configClass)) {
		this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
	}
	else {
		this.importStack.push(configClass);
		try {
			for(SourceClass candidate: importCandidates) {// Process a class of type ImportSelectorif(candidate.isAssignable(ImportSelector.class)) { // Candidate class is an ImportSelector -> delegate to it to determine imports Class<? > candidateClass = candidate.loadClass(); ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class); ParserStrategyUtils.invokeAwareMethods( selector, this.environment, this.resourceLoader, this.registry); // The other Selector type we talked about above, we can think of as lazy loadingif(Selector instanceof deferredImportSelectors) {// The method internally stores the selector into a collection of deferredImportSelectors this.deferredImportSelectorHandler.handle( configClass, (DeferredImportSelector) selector); }else {
						String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
						Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
						processImports(configClass, currentSourceClass, importSourceClasses, false); }}else if(candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { // Candidate class is an ImportBeanDefinitionRegistrar -> // delegate to it to register additional bean definitions Class<? > candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); ParserStrategyUtils.invokeAwareMethods( registrar, 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 / / the above directly to the official English hard translation, the current class not ImportSelector or ImportBeanDefinitionRegistrar type, Let it go directly @ the handling process of the Configuration class enclosing importStack. RegisterImport (currentSourceClass. For getMetadata (), candidate.getMetadata().getClassName()); processConfigurationClass(candidate.asConfigClass(configClass)); } } } 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(); }}}Copy the code

To sum up, the general flow is that the return value of the ImportSelector interface is recursively parsed, and then the full name of the parsed class is eventually processed in accordance with @con figuration.

ImportSelector summary

SpringBoot owes much of its out-of-the-box nature to ImportSelector.

Spring Boot source code analysis

Spring Boot has made some extensions to Spring.

  1. An @enableAutoConfiguration annotation is declared in the SpringBootApplication annotation for SpringBoot

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication { Copy the code

@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration { Copy the code

Next to AutoConfigurationImportSelector source analysis

AutoConfigurationImportSelector

AutoConfigurationImportSelector is selectImports implementation class, let’s look at selectImports method

selectImports

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if(! this.isEnabled(annotationMetadata)) {return NO_IMPORTS;
    } else {
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
        returnStringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }}Copy the code

The main logic of the method are within the getAutoConfigurationEntry method

getAutoConfigurationEntry

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
    if(! this.isEnabled(annotationMetadata)) {return EMPTY_ENTRY;
    } else{ AnnotationAttributes attributes = this.getAttributes(annotationMetadata); / / 2.1 by getCandidateConfigurations method to obtain all need to load bean a List < String > configurations = this.getCandidateConfigurations(annotationMetadata, attributes); // Delete duplicates = this. RemoveDuplicates (configurations); // Get the bean that doesn't need to be loaded, We can through the spring. The autoconfigure. Exclude configuration Set < String > exclusions = this. GetExclusions (annotationMetadata, attributes); this.checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = this.filter(configurations, autoConfigurationMetadata); / / send events, notify all the intercepting AutoConfigurationImportListener enclosing fireAutoConfigurationImportEvents (configurations, exclusions);returnnew AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions); }}Copy the code

Then look at the source above getCandidateConfigurations method in the call

getCandidateConfigurations

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {/ / getSpringFactoriesLoaderFactoryClass here () returns the eventually EnableAutoConfiguration. Class List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()); Assert.notEmpty(configurations,"No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
} 

Copy the code

Can be seen from the above logic, but it will get the bean’s channel in SpringFactoriesLoader. Within loadFactoryNames SpringFactoriesLoader. LoadFactoryNames (enclosing getSpringFactor iesLoaderFactoryClass(), this.getBeanClassLoader());

SpringFactoriesLoader

loadFactoryNames

public static List<String> loadFactoryNames(Class<? > factoryClass, String factoryClassName = factoryClass.getName();return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
} 

Copy the code

loadSpringFactories

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if(result ! = null) {return result;
    } else{try {// Get all the meta-INF /spring.factories in the project and assemble them into Map Enumeration<URL> urls = classLoader! = null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
            LinkedMultiValueMap result = new LinkedMultiValueMap();

            while(urls.hasMoreElements()) {
                URL url = (URL)urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                Iterator var6 = properties.entrySet().iterator();

                while(var6.hasNext()) { Entry<? ,? > entry = (Entry)var6.next(); String factoryClassName = ((String)entry.getKey()).trim(); String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue()); int var10 = var9.length;for(int var11 = 0; var11 < var10; ++var11) {
                        String factoryName = var9[var11];
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }

            cache.put(classLoader, result);
            return result;
        } catch (IOException var13) {
            throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13); }}}Copy the code

Each JAR can define its own meta-INF /spring.factories, and the beans defined in the Spring. factories are automatically loaded when the JAR is loaded. We can take a look at this configuration file for Spring Boot.

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# 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,\ Copy the code

Therefore, we can modify the above example by creating the file in the resouces directory and adding the following

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
top.chenfu.auto.config.MyConfig   Copy the code

Remove the @import or custom @enablexxx annotation from the startup class