Analysis of SpringBoot Automatic Assembly Principle

Keywords: Java Spring SpringBoot Attribute JDBC

This article contains: SpringBoot's automatic configuration principle and how to customize SpringBootStar, etc.

We know that when using SpringBoot, we only need to start a Web program directly as follows:

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

It's hardly convenient compared to the cumbersome configuration we used to use with regular Spring, do you know how SpringBoot works with these?

First we see a @SpringBootApplication annotation above the class

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackageClasses"
    )
    Class<?>[] scanBasePackageClasses() default {};
}

There are still a lot of things on this note. Let's take a look at two simple warm-ups

@ComponentScan comment

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

We are all familiar with this annotation, which automatically scans and loads qualified beans into containers, and by default scans the package in which the declared class resides to start scanning, for example:
Classes under packages such as cn.shiyujun.controller, cn.shiyujun.service, and so on, can be scanned if the @ComponentScan annotation is marked on the class cn.shiyujun.Demo

This note contains the following properties:

basePackages: Specify multiple package names to scan
 basePackageClasses: sweeps the package to which the specified class and interface belong
 excludeFilters: Specify a filter that will not be scanned
 includeFilters: Specifies the filter to scan
 lazyInit: Is the registered scanned bean s set to lazy load
 nameGenerator: automatically name scanned bean s
 resourcePattern: Controls the class files available for scanning
 scopedProxy: Specifies whether the proxy should be scanned
 scopeResolver: Specifies the scope of the scan bean
 useDefaultFilters: Turn on class detection for @Component, @Repository, @Service, @Controller

@SpringBootConfiguration annotation

This annotation is simpler, it's just an encapsulation of the Configuration annotation

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

EnableAutoConfiguration comment

This annotation is a big deal. The SpringBoot convention is larger than configuration, which is why the focus of this article is on automatic assembly.

@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

In a nutshell, the meaning of this annotation is that @Import annotation is used to inject all bean s that meet the requirements of automatic assembly into the IOC container. The principle of @Import annotation is not explained here anymore. Interested students can refer to this article: Spring @Import Annotation Source Parsing

Enter the class AutoConfigurationImportSelector and observe its selectImports method. When this method is executed, Spring injects all the classes in the fully qualified name array of the class returned by this method into the IOC container

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
        }
    }

Look at the code above:

  1. The first line, if, first determines if the auto-assembly function is disabled in the current system. The code to determine this is as follows:
 protected boolean isEnabled(AnnotationMetadata metadata) {
        return this.getClass() == AutoConfigurationImportSelector.class ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
    }
  1. If auto-assembly is disabled on the current system, the following empty array will be returned and subsequent bean s will not be injected
private static final String[] NO_IMPORTS = new String[0];
  1. If auto-assembly is not disabled then the else branch is entered. The first step is to load all Spring predefined configuration condition information in the META-INF/spring-autoconfigure-metadata.properties file under the org.springframework.boot.autoconfigure package.
  2. The main meaning of these configuration conditions is roughly the following: If you want to automatically assemble a class, which classes or profiles do you think exist first, etc. These conditions are judged mainly by the @ConditionalXXX annotation, which can be referred to in this article for the @ConditionalXXX series of annotations: SpringBoot Conditional Comment@Conditional

  3. The content in this file is formatted like this:
    ```
    org.springframework.boot.actuate.autoconfigure.web.servlet.WebMvcEndpointChildContextConfiguration.ConditionalOnClass=org.springframework.web.servlet.DispatcherServlet
    org.springframework.boot.actuate.autoconfigure.metrics.jdbc.DataSourcePoolMetricsAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,io.micrometer.core.instrument.MeterRegistry
    org.springframework.boot.actuate.autoconfigure.flyway.FlywayEndpointAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration
 3. The specific loading code is not listed and cannot be just a read profile
 4. Here is the result after loading:
 ![file](https://img2018.cnblogs.com/blog/1431571/201909/1431571-20190920090843541-1514005316.jpg)
4. Get the exclude and excludeName attributes on the `@EnableAutoConfiguration'comment, both of which are used to exclude classes
 5. This is another key step. You can see that there is a file, spring.factories, above the spring-autoconfigure-metadata.properties file in the picture just now. This file does not just exist in the `org.springframework.boot.autoconfigure` package, it may exist in all packages, so this oneThe step is to load all the spring.factories files for the entire project.This file is formatted like this

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframework.boot.actuate.autoconfigure.amqp.RabbitHealthIndicatorAutoConfiguration,\org.springframework.boot.actuate.autoconfigure.audit.AuditAutoConfiguration,\org.springframework.boot.actuate.autoconfigure.audit.AuditEventsEndpointAutoConfiguration

6. There is a point of knowledge here. Star in SpringBoot depends on this file. If we need to customize a Star in SpringBoot, we can create a new spring.factories file in the META-INF folder of our project.

org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.shiyujun.TestAutoConfiguration

This automatically injects our `TestAutoConfiguration` class into the Spring container when other projects depend on our project
 7. Delete duplicate auto-configuration classes
 8. The following three lines remove the configuration classes that we specified to be excluded
 9. The logic of this line is slightly more complex, primarily to determine whether the `@ConditionalXXX'series of annotations on each configuration class meet the requirements based on the configuration condition information loaded
 10. The final step is to publish the auto-assembly completion event and return the fully qualified names of all the classes that can be auto-assembled


Here we've got some idea of how SpringBoot auto-assembly works, but it always feels worse. Let's take a look at one of these auto-assembly classes that we're familiar with about Servlet:

@Configuration
@ConditionalOnWebApplication(
type = Type.SERVLET
)
public class ServletEndpointManagementContextConfiguration {
public ServletEndpointManagementContextConfiguration() {
}

@Bean
public ExposeExcludePropertyEndpointFilter<ExposableServletEndpoint> servletExposeExcludePropertyEndpointFilter(WebEndpointProperties properties) {
    Exposure exposure = properties.getExposure();
    return new ExposeExcludePropertyEndpointFilter(ExposableServletEndpoint.class, exposure.getInclude(), exposure.getExclude(), new String[0]);
}

@Configuration
@ConditionalOnClass({ResourceConfig.class})
@ConditionalOnMissingClass({"org.springframework.web.servlet.DispatcherServlet"})
public class JerseyServletEndpointManagementContextConfiguration {
    public JerseyServletEndpointManagementContextConfiguration() {
    }

    @Bean
    public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier) {
        return new ServletEndpointRegistrar(properties.getBasePath(), servletEndpointsSupplier.getEndpoints());
    }
}

@Configuration
@ConditionalOnClass({DispatcherServlet.class})
public class WebMvcServletEndpointManagementContextConfiguration {
    private final ApplicationContext context;

    public WebMvcServletEndpointManagementContextConfiguration(ApplicationContext context) {
        this.context = context;
    }

    @Bean
    public ServletEndpointRegistrar servletEndpointRegistrar(WebEndpointProperties properties, ServletEndpointsSupplier servletEndpointsSupplier) {
        DispatcherServletPathProvider servletPathProvider = (DispatcherServletPathProvider)this.context.getBean(DispatcherServletPathProvider.class);
        String servletPath = servletPathProvider.getServletPath();
        if (servletPath.equals("/")) {
            servletPath = "";
        }

        return new ServletEndpointRegistrar(servletPath + properties.getBasePath(), servletEndpointsSupplier.getEndpoints());
    }
}

}
``Looking at the code for the entire class from top to bottom, you will see that these auto-assembly routines are the same 1. If you are in a Servlet environment, then assemble this bean 2. When class ResourceConfig exists and class Dispatcher Servlet does not exist, match Jersey Servlet Endpoint ManagementContextConfiguration 3. When Dispatcher Servle existsMatching WebMvcServletEndpointManagementContextConfiguration in Class t`

  1. Next, if an interviewer asks you, will you?

Posted by howard-moore on Thu, 19 Sep 2019 18:52:49 -0700