In depth analysis of the implementation principle of Spring Boot automatic assembly mechanism

Keywords: Java

In the previous analysis, the Spring Framework has been committed to solving a problem, that is, how to make bean management easier, and how to make developers pay less attention to the configuration of some basic beans, so as to realize automatic assembly. Therefore, the so-called automatic assembly is actually how to automatically load beans into the Ioc container.

In fact, in spring 3.x, the emergence of Enable module driven annotation has a certain prototype of automatic assembly, and the real realization of this mechanism is the emergence of conditional conditional annotation in spirng 4.x. ok, let's take a look at the automatic assembly of spring boot.

Demonstration of automatic assembly

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> 
spring:
    redis:
      host: 127.0.0.1 
      port: 6379
 @Autowired
    private RedisTemplate<String,String>redisTemplate;

Add the starter in the following order, and then add the configuration. You can use RedisTemplate? Have you thought about a question, why can RedisTemplate be injected directly? When was it added to the Ioc container? This is automatic assembly. Automatic assembly enables the dependent package related bean s under the classpath to be automatically loaded into the Spring Ioc container. How do you do this?

In depth analysis of EnableAutoConfiguration

The main function of EnableAutoConfiguration is to help springboot applications load all qualified @ Configuration configurations into the IoC container created and used by the current springboot.

Returning to the enable autoconfiguration annotation, we find that its import is like this

@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

However, judging from the import annotation above EnableAutoCOnfiguration, this does not introduce another Configuration. It's an ImportSelector. What is this?

What is AutoConfigurationImportSelector?

The Enable annotation can not only realize the integration of multiple configurations as the case demonstrated above, but also realize some complex scenarios. For example, different types of bean s can be activated according to the context, and the @ Import annotation can configure three different class es

  1. The first is based on ordinary beans or beans with @ Configuration, as demonstrated earlier
  2. Implement the ImportSelector interface for dynamic injection

Implement the ImportBeanDefinitionRegistrar interface for dynamic injection

CacheService

public class CacheService {
}

LoggerService

public class LoggerService {
}

EnableDefineService

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented 
@Inherited  --Allow to be inherited
@Import({GpDefineImportSelector.class})
public @interface EnableDefineService {

    String[] packages() default "";
}

GpDefineImportSelector

public class GpDefineImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //Gets the details of the specified annotation. We can return different class es according to the attributes configured in the annotation,
        //Thus, the purpose of dynamically opening different functions can be achieved
    
annotationMetadata.getAllAnnotationAttributes(EnableDefineService.class.getName(),true)
            .forEach((k,v) -> {
                log.info(annotationMetadata.getClassName());
                log.info("k:{},v:{}",k,String.valueOf(v));
            });
        return new String[]{CacheService.class.getName()};
    }
}

EnableDemoTest

@SpringBootApplication
@EnableDefineService(name = "gupao",value = "gupao")
public class EnableDemoTest {
    public static void main(String[] args) {
        ConfigurableApplicationContext ca=SpringApplication.run(EnableDemoTest.class,args);
        System.out.println(ca.getBean(CacheService.class));
        System.out.println(ca.getBean(LoggerService.class));
    }
}

After understanding the basic principle of the selector, it is very simple to analyze the principle of AutoConfigurationImportSelector later. It is also the dynamic loading of bean s in essence.

@Implementation principle of EnableAutoConfiguration annotation

Understanding EnableAutoConfiguration is easier after understanding ImportSelector and ImportBeanDefinitionRegistrar

It imports the configuration class of the bean provided by the third party through import: AutoConfigurationImportSelector

@Import(AutoConfigurationImportSelector.class)

From the name, you can guess that it is based on ImportSelector to realize the loading function based on dynamic bean s. We talked about the working principle of Springboot @Enable * annotation before. The array (full class name of the class) returned by selectImports of the ImportSelector interface will be included in the spring container.

You can guess that the implementation principle here must be the same. Locate the selectImports method in the AutoConfigurationImportSelector class

selectImports

public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
// Load AutoConfigurationMetadata from the configuration file (spring-autoconfigure-metadata.properties)
   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
         .loadMetadata(this.beanClassLoader);
// Get all candidate configuration classes EnableAutoConfiguration
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
         autoConfigurationMetadata, annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

getAutoConfigurationEntry

protected AutoConfigurationEntry getAutoConfigurationEntry(
      AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
//Gets the attribute in the meta annotation
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
//Use springfactoryesloader to load META-INF\spring.factories under the classpath path,
//Key = value corresponding to org.springframework.boot.autoconfigure.enableautoconfiguration
   List<String> configurations = getCandidateConfigurations(annotationMetadata,
         attributes);
//duplicate removal
   configurations = removeDuplicates(configurations);
//Apply exclusion property
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
//Filter: check the annotation @ ConditionalOnClass on the candidate configuration class. If the required class does not exist, the candidate class will be filtered and not loaded
   configurations = filter(configurations, autoConfigurationMetadata);
   //Broadcast event
fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

In essence, EnableAutoConfiguration will help springboot applications load all @ Configuration compliant configurations into the IoC container created by the current springboot, which is supported by a tool class springfactoryesloader provided by the Spring framework. The @ Conditional annotation provided by Spring is used to selectively filter the bean s to be loaded

SpringFactoriesLoader

In order to supplement the foundation for you, I'll briefly analyze the use of the tool class SpringFactoriesLoader. In fact, its principle is the same as that of SPI mechanism in java, but it is better than SPI in that it does not load all classes at one time, but according to the key.

First, the role of springfactoryesloader is to load the corresponding classes into the spring IoC container according to the key from the classpath/META-INF/spring.factories file. Next, let's practice it

Create external project jar

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>4.3.13.RELEASE</version>
</dependency>

Create bean and config

public class GuPaoCore {
    public String study(){
        System.out.println("good good study, day day up");
        return "GuPaoEdu.com";
    }
}
@Configuration
public class GuPaoConfig {
    @Bean
    public GuPaoCore guPaoCore(){
        return new GuPaoCore();
    }
}

Create another project (spring boot)

Package the previous project into a jar, and the current project depends on the jar package

<dependency>
    <groupId>com.gupaoedu.practice</groupId>
    <artifactId>Gupao-Core</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

Get the properties in the dependent package through the following code

The running result will report an error because GuPaoCore is not loaded by Spring's IoC container, that is, it is not imported by EnableAutoConfiguration

@SpringBootApplication
public class SpringBootStudyApplication {
    public static void main(String[] args) throws IOException {
        ConfigurableApplicationContext ac=SpringApplication.run(SpringBootStudyApplication.class, args);
        GuPaoCore gpc=ac.getBean(GuPaoCore.class);
        System.out.println(gpc.study());
    }
}

Solution

Create a new folder META-INF under Gupao core project resources, and a new spring.factories file under the folder. In the file, key is the full path of the custom configuration class EnableAutoConfiguration, and value is the full path of the configuration class

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.gupaoedu.practice.GuPaoConfig

Repackage and rerun the SpringBootStudyApplication class.

It can be found that the class we write is loaded in.

@Implementation principle of EnableAutoConfiguration annotation

Understanding EnableAutoConfiguration is easier after understanding ImportSelector and ImportBeanDefinitionRegistrar

It imports the configuration class of the bean provided by the third party through import: AutoConfigurationImportSelector

@Import(AutoConfigurationImportSelector.class)

From the name, you can guess that it is based on ImportSelector to realize the loading function based on dynamic bean s. We talked about the working principle of Springboot @Enable * annotation before. The array (full class name of the class) returned by selectImports of the ImportSelector interface will be included in the spring container.

You can guess that the implementation principle here must be the same. Locate the selectImports method in the AutoConfigurationImportSelector class

selectImports

public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
// Load AutoConfigurationMetadata from the configuration file (spring-autoconfigure-metadata.properties) 
   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
         .loadMetadata(this.beanClassLoader);
// Get all candidate configuration classes EnableAutoConfiguration
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
         autoConfigurationMetadata, annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

getAutoConfigurationEntry

protected AutoConfigurationEntry getAutoConfigurationEntry(
      AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
//Gets the attribute in the meta annotation
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
//Use springfactoryesloader to load META-INF\spring.factories under the classpath path,
//Key = value corresponding to org.springframework.boot.autoconfigure.enableautoconfiguration
   List<String> configurations = getCandidateConfigurations(annotationMetadata,
         attributes);
//duplicate removal
   configurations = removeDuplicates(configurations);
//Apply exclusion property
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
//Filter: check the annotation @ ConditionalOnClass on the candidate configuration class. If the required class does not exist, the candidate class will be filtered and not loaded
   configurations = filter(configurations, autoConfigurationMetadata);
   //Broadcast event
fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

In essence, EnableAutoConfiguration will help springboot applications load all @ Configuration compliant configurations into the IoC container created by the current springboot, which is supported by a tool class springfactoryesloader provided by the Spring framework. The @ Conditional annotation provided by Spring is used to selectively filter the bean s to be loaded

Copyright notice: unless otherwise stated, all articles on this blog adopt CC BY-NC-SA 4.0 license agreement. Reprint please indicate from Mic to take you to learn architecture!
If this article is helpful to you, please pay attention and praise. Your persistence is the driving force of my continuous creation. Welcome to WeChat public official account for more dry cargo.

Posted by FezEvils on Thu, 25 Nov 2021 21:07:30 -0800