SpringBoot2 automatic configuration principle and source code analysis

1, the introduction

Before SpringBoot became popular, programmers mostly used SSM framework integration for WEB backend development. This approach is cumbersome, requiring manually importing a lot of packages, configuring a lot of XML files, and just setting up the environment takes a long time.

With the popularity of convention over configuration, SpringBoot simplifies web development and makes it easy for beginners to get started. There are three core ideas behind SpringBoot:

  • To help developers quickly integrate third-party frameworks, Maven relies on encapsulation and custom Starter.
  • Get rid of XML completely and adopt a pure annotation approach. The idea is that SpringBoot is actually a wrapper around the native annotation implementation of the Spring architecture.
  • Instead of using an external container, use an embedded Web container by creating a Tomcat server in the Java language and handing tomcat the local class files to load.

Spring Boot uses a variety of scenario starter to help us automatically introduce Jar dependencies and control their versions. The main principle of Spring Boot to achieve this function is automatic configuration. So how does it work?

Basic project structure:

Complete base POM file:


      
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lemon</groupId>
    <artifactId>SpringBoot-01</artifactId>
    <version>1.0 the SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.6.3</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
Copy the code

2. Spring Boot 2. X principle analysis

2.1, @ SpringBootApplication:

First let’s take a look at the Spring Boot main program: this is the Boot class that we will use to develop any Spring Boot project

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) { SpringApplication.run(MainApplication.class, args); }}Copy the code

Here we can see the @SpringBootApplication annotation: this core composite annotation is used to annotate the SpringBoot main program class, indicating that the class is the SpringBoot execution entry and one of the flags indicating that the project is a SpringBoot project. Click on this note:

We can see that the annotation annotates several annotations simultaneously, And @SpringBootConfiguration, @EnableAutoConfiguration and @ComponentScan are the same as @SpringBootApplication annotations. The @ComponentScan annotation specifies which Spring annotations to scan; I already said that

Let’s focus on these three annotations, which are shown below

2.1.1, @ SpringBootConfiguration

@SpringBootConfiguration: SpringBootConfiguration class annotation. Since @Configuration is annotated, it indicates that the annotated class is the SpringBootConfiguration class. The annotated class will be registered with the Spring IOC container. So our main program is also a configuration class, and a core configuration class

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor( annotation = Configuration.class )
    boolean proxyBeanMethods(a) default true;
}
Copy the code

@Configuration: Spring Configuration class annotation, equivalent to the tag in spring.xml. Inside the @Component annotation is a Configuration class, indicating that it is essentially a Component in the Spring container.

2.1.2, @ ComponentScan

Referring to the original Spring, this annotation specifies which packages to scan for, and the classes in the packages to be automatically created by the Spring framework and stored as components in the container

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
Copy the code

Example: The attributes in the following annotation define two scanners,

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

2.1.3 @enableAutoConfiguration [Core]

EnableAutoConfiguration: note to enable the automatic configuration function.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<? >[] exclude()default {};

    String[] excludeName() default {};
}
Copy the code

Here we can see two important comments: @ AutoConfigurationPackage / @ Import, they are @ EnableAutoConfiguration synthetic annotation

@AutoConfigurationPackage

@autoConfigurationPackage: configures packages automatically, specifying the default configuration management rules for packages

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default{}; Class<? >[] basePackageClasses()default {};
}
Copy the code

The Registrar annotation is via the @import annotation to Import the Registrar class into the Registrar container to Import all the components in the sibling and sub-package of the main application class. So how do you import this set of components? Click on the Registrar. Class class

static class Registrar implements ImportBeanDefinitionRegistrar.DeterminableImports {
    Registrar() {
    }
    // Batch register components for containers
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
    }

    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(newAutoConfigurationPackages.PackageImports(metadata)); }}Copy the code

Which has a method: AutoConfigurationPackages. The register (), the method is batch register all package components inside

Debug this method to check how batch registration works:

  • Among them, we found thatregisterBeanDefinitionsThis method takes one argumentAnnotationMetadata metadata
  • Among themAnnotationMetadata metadataIs the annotation@AutoConfigurationPackageRepresents the meta information of@AutoConfigurationPackageWhere is this annotation, and what is the value of the property in it, as followsAnnotation in the main program classon

Second, AutoConfigurationPackages. Register there is such a parameter () method, which found that a piece of code:

(String[])
(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])
Copy the code

Among them: new AutoConfigurationPackages. PackageImports (metadata)) getPackageNames () : To get the package name by taking the meta information from the @AutoConfigurationPackage annotation: Select the code and right-click Evaluate

The result is a package name.

ToArray (new String[0])); Encapsulate the package names in an array, and then register them all, hence the previous conclusion:

@AutoConfigurationPackageAnnotations are made by@ImportNote the importRegistrarClass this component to take advantage ofRegistrarImport all components of the main program class’s siblings and subpackages into the container. This solves the problem of why the default scan is the main program’s package and its subpackages

@Import(AutoConfigurationImportSelector.class)

The previous @AutoConfigurationPackage determines that the main package and its subpackages are imported into the container. So what components are imported? So this annotation helps us solve this problem.

  • @Import: Imports automatically configured components
  • AutoConfigurationImportSelector: Auto-configure selector, which is our auto-configured core class, through which Spring Boot determines which [131] components will be added to the Spring container. The core method is selectImports. Click on source code analysis to find the core method
public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            returnStringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }}Copy the code

Found a getAutoConfigurationEntry inside (annotationMetadata) method, * * is to use this method to the container, bulk import some of the specific components in the * * turn down, find the implementation of this method, the Debug Debug this method:

Pass all the way to the code shown below:

GetCandidateConfigurations (annotationMetadata, attributes) is to get all candidate configuration, stored in the form of collection, is set to get the list of configurations, Configurations You can see in the following code that this uses a number of things [for example, removeDuplicates remove some duplicates, checkExcludedClasses exclude some things… then encapsulates them into objects and returns them]. You can see that this configurations is important. There are 131 of them

Note All 131 components are imported into the container by default.

So how does import work? The debug debugging a List < String > configurations = this. GetCandidateConfigurations (annotationMetadata, attributes);

Step into the method

You can see that he’s using the SpringFactoriesLoader factory to load something, click on it

SpringFactoriesLoader. LoadFactoryNames () method, which is as follows:

You can click on the loadSpringFactories method as followsMap<String, List<String>> loadSpringFactories(ClassLoader classLoader)Loading gets all the components, so where are they loaded from? You can see from theMETA-INF/spring.factoriesLocation to load a file, thus default to scan all of our current systemMETA-INF/spring.factoriesLocation file

Take the Springboot package as an example:

Other than this package, our core packageSpring - the boot - autoconfigure - 2.5.2. JarThere are also specified files

These are our 131 auto-configuration classes, and this file contains all the configuration classes that springboot will load into the container

2.2 Process Summary:

  1. Using getAutoConfigurationEntry (annotationMetadata); Import a batch of components into the container, 131

  2. Call a List < String > configurations = this. GetCandidateConfigurations (annotationMetadata, attributes); Get all the configuration classes (components) that need to be imported into the container

  3. LoadSpringFactories (ClassLoader) with Map

    > loadSpringFactories(ClassLoader). You can see that a file is loaded from the meta-INF /spring.factories location, and the default is to scan all the meta-INF /spring.factories in our current system
    ,>

Conclusion:

The “/ meta-INF /spring.factories” file does not work. The “/ meta-inf /spring.factories” file does not work. The “/ meta-inf /spring.factories” file does not work. Our autologize will take effect and the configuration will be successful

Steps:

  • Springboot gets the specified value from the/meta-INF/spring.Factories classpath when it starts

  • Import these auto-configuration classes into the container, and the auto-configuration classes will take effect and help us auto-configure

  • We used to need to configure things automatically, but SpringBoot now does it for us

  • The whole javaEE, solution and auto-configuration stuff is in spring-boot-autoconfigure-2.1.2.jar

  • It will return all the components that need to be imported as class names, and those components will be added to the container

  1. There are also a lot of XXXAutoConfiguration files in the container, and it is these classes that import all the components needed for these scenarios into the container and automatically configure them

  2. The auto-configuration class saves us the task of writing configuration files manually

2.3 loading on demand, conditional assembly

The spring.factories file has all the auto-configuration classes in it. If the spring.factories file does not have all the auto-configuration classes in it, you need to import the corresponding Start class to make it work. Take AopAutoConfiguration for example. It is also one of the component classes that we load into the container registry by default, but we find that it is not in the container because of the conditional rule

ConditionOn XXX (@conditionon XXX) although all of our 131 scenarios are loaded by default when activated, ConditionOn XXX is conditional. Eventually, it will be configured as needed, not all loaded into the container, that is, must follow the conditions specified.

Since our demo project did not introduce a dependency JAR package for aOP-related scenarios, we were unable to implement our AOP aspect functionality due to conditional rules: As shown in the figure below, the conditions must be met, i.e. the presence of the · advice.class · class in our classpath, the following JDK proxy, etc., to be effective, and the AOP functionality to be effective.

@Configuration( proxyBeanMethods = false )
    @ConditionalOnClass({Advice.class})
    static class AspectJAutoProxyingConfiguration {
        AspectJAutoProxyingConfiguration() {
        }

        @Configuration( proxyBeanMethods = false )
        @EnableAspectJAutoProxy( proxyTargetClass = true )
        @ConditionalOnProperty( prefix = "spring.aop", name = {"proxy-target-class"}, havingValue = "true", matchIfMissing = true )
        static class CglibAutoProxyConfiguration {
            CglibAutoProxyConfiguration() {
            }
        }

        @Configuration( proxyBeanMethods = false )
        @EnableAspectJAutoProxy( proxyTargetClass = false )
        @ConditionalOnProperty( prefix = "spring.aop", name = {"proxy-target-class"}, havingValue = "false" )
        static class JdkDynamicAutoProxyConfiguration {
            JdkDynamicAutoProxyConfiguration() {
            }
        }
    }
Copy the code

Added: Some annotations in the AutoConfig config class are useful:

3, SpringBoot2 automatic configuration process demonstration

Take AopAutoConfiguration as an example. It is also one of the component classes that SpringBoot loads into the container registration by default, but we find that there is no such component in the container. The reason is because of the conditional rule

Let’s look at the automatic configuration process and also demonstrate the workflow of some configuration classes in effect. Below is the source code for this class


// The first part
@Configuration( proxyBeanMethods = false )
@ConditionalOnProperty( prefix = "spring.aop", name = {"auto"}, havingValue = "true", matchIfMissing = true )
public class AopAutoConfiguration {
    public AopAutoConfiguration(a) {}// Part 2
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnMissingClass({"org.aspectj.weaver.Advice"})
    @ConditionalOnProperty( prefix = "spring.aop", name = {"proxy-target-class"}, havingValue = "true", matchIfMissing = true )
    static class ClassProxyingConfiguration {
        ClassProxyingConfiguration() {
        }

        @Bean
        static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying(a) {
            return (beanFactory) -> {
                if (beanFactory instanceofBeanDefinitionRegistry) { BeanDefinitionRegistry registry = (BeanDefinitionRegistry)beanFactory; AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); }}; }}// Part 3
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({Advice.class})
    static class AspectJAutoProxyingConfiguration {
        AspectJAutoProxyingConfiguration() {
        }

        @Configuration( proxyBeanMethods = false )
        @EnableAspectJAutoProxy( proxyTargetClass = true )
        @ConditionalOnProperty( prefix = "spring.aop", name = {"proxy-target-class"}, havingValue = "true", matchIfMissing = true )
        static class CglibAutoProxyConfiguration {
            CglibAutoProxyConfiguration() {
            }
        }

        @Configuration( proxyBeanMethods = false )
        @EnableAspectJAutoProxy( proxyTargetClass = false )
        @ConditionalOnProperty( prefix = "spring.aop", name = {"proxy-target-class"}, havingValue = "false" )
        static class JdkDynamicAutoProxyConfiguration {
            JdkDynamicAutoProxyConfiguration() {
            }
        }
    }
}
Copy the code

3.1 Whether the AopAutoConfiguration component takes effect — load it on demand

ConditionalOnProperty (@conditionalonProperty) ConditionalOnProperty (@conditionalonProperty) ConditionalOnProperty (@conditionalonProperty) Determine if the configuration prefixed with spring.aop exists in the configuration file xxxxProperties and only in the property spring.aop.auto (matchIfMissing=true: Even if you don’t, I assume you did) or explicitly true, so the code inside the AopAutoConfiguration component takes effect.

@Configuration( proxyBeanMethods = false )
@ConditionalOnProperty( prefix = "spring.aop", name = {"auto"}, havingValue = "true", matchIfMissing = true )
public class AopAutoConfiguration {
    public AopAutoConfiguration(a) {}Copy the code

2. Look at Part 3

 @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({Advice.class})
    static class AspectJAutoProxyingConfiguration {
        AspectJAutoProxyingConfiguration() {
        }

        @Configuration(
            proxyBeanMethods = false
        )
        @EnableAspectJAutoProxy(
            proxyTargetClass = true
        )
        @ConditionalOnProperty(
            prefix = "spring.aop",
            name = {"proxy-target-class"},
            havingValue = "true",
            matchIfMissing = true
        )
        static class CglibAutoProxyConfiguration {
            CglibAutoProxyConfiguration() {
            }
        }

        @Configuration(
            proxyBeanMethods = false
        )
        @EnableAspectJAutoProxy(
            proxyTargetClass = false
        )
        @ConditionalOnProperty(
            prefix = "spring.aop",
            name = {"proxy-target-class"},
            havingValue = "false"
        )
        static class JdkDynamicAutoProxyConfiguration {
            JdkDynamicAutoProxyConfiguration() {
            }
        }
    }
Copy the code

ConditionalOnClass (@conditionalonClass) ConditionalOnClass specifies that advice. class is valid only if it exists in the classpath. Pay attention to the Advice we class is exist in aspectj package under the import org. Aspectj. Weaver. Advice;] And since we did not import the AspectJ package, the code for this class in this section will not take effect

3. Look at part 2, the first Static Class section in the figure above

 @Configuration( proxyBeanMethods = false )
    @ConditionalOnMissingClass({"org.aspectj.weaver.Advice"})
    @ConditionalOnProperty( prefix = "spring.aop", name = {"proxy-target-class"}, havingValue = "true", matchIfMissing = true )
    static class ClassProxyingConfiguration {
        ClassProxyingConfiguration() {
        }

        @Bean
        static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying(a) {
            return (beanFactory) -> {
                if (beanFactory instanceofBeanDefinitionRegistry) { BeanDefinitionRegistry registry = (BeanDefinitionRegistry)beanFactory; AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); }}; }}Copy the code

First of all, it is also a configuration class, on one condition annotations @ ConditionalOnMissingClass, parameters when the system class path does not have a org). The aspectj weaver. Advide the code will only take effect when the class [because we are just not this advice, ConditionalOnProperty (” ConditionalOnProperty “); / / ConditionalOnProperty (” ConditionalOnProperty “); / / ConditionalOnProperty (” ConditionalOnProperty “); / / ConditionalOnProperty (” ConditionalOnProperty “) It turns on AOP functionality by default and is a simple AOP functionality. [With interface and annotation]

Summary: Under our conditional rules: some of the AOP auto-configuration classes work and some don’t. So to sum up, the AopAutoConfiguration configuration class component does not work because the AspectJ package is not imported and the Advice class does not exist, but it can do simple AOP functions

3.2. Modify the default Settings

Taking SpringMVC as an example, the Web scenario is introduced

Look at the automatic HttpEncodingAutoConfiguration configuration

By default, SpringBoot sets all the components in the bottom layer (we put in character codes, character parser names, in case you’re messing around). But if the user configures the user first: MVC character encoders, for example. Convention greater than Configuration

Native configuration class @ ConditionalOnMissingBean this annotation can own configuration, as shown in the following: the native is not effective, just use my own definition of filter, loaded into the container

@Bean
public CharacterEncodingFilter filter( ){
    return null;
}
Copy the code

3.3 summary:

  • SpringBoot starts by loading all the automatic configuration classes xxxxxAutoConfiguration
  • Each automatic configuration class takes effect according to condition rules and is configured as required. By default, the values specified in the configuration file are bound. XxxxProperties. XxxProperties is bound to the configuration file appliction.properties
  • A configuration class in effect assembles many components into the container
  • As long as you have these components in the container, you have the equivalent of these functions
  • The user configuration class takes effect by itself,
  • Customized configuration
    • Method 1: users directly themselves@BeanReplace the underlying components
    • Method 2: the user can modify the configuration file to see what value the component is fetching.
    • For example, modify the character encoding below

A detailed description of each configuration can be viewed below

Presentation:

Let’s say I want to configure the cache

Detailed explanation of each parameter

4. Use and best practice of Spring Boot

4.1 SpringBoot development practice

In the future, we will develop the SpringBoot project by following the following steps

  • Introduce scenario dependencies [including third parties]

    • For example, WEB development introduces WEB scene dependencies

    • IO /spring-boot…

  • View what is automatically configured (Optional)

    • Method 1: Analyze by yourself. The automatic configuration of the imported scenario generally takes effect
    • Method 2: Configuration filesapplication.propertiesWrite in thedebug=trueEnable automatic configuration report. Negative (not effective) \Positive (effective)
  • Whether it needs to be modified

    • Refer to the documentation to modify the configuration items: docs.spring. IO /spring-boot…

The default name is banner.txt.banner.gif, etc. If not, just change it

  • Classpath: Classpath refers to the resource directory
  • Do your own analysis: which prefixes of the configuration file are bound to xxxxProperties.
  • Add or replace components @bean, @Component…
  • Customizer XXXXXCustomizer;

.