2
头图

Spring Boot自动装配

In the previous analysis, 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 as much as possible, 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 the spring 3.x version, the Enable module-driven annotation has already had a certain prototype of automatic assembly, and the real realization of this mechanism is the emergence of conditional annotations in the spirng 4.x version. 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, then add the configuration, can it be used by using RedisTemplate? Have you ever thought about a question, why can RedisTemplate be injected directly? When was it added to the Ioc container? This is automatic assembly. Auto-assembly can make the beans related to the package dependent on the classpath be automatically loaded into the Spring Ioc container. How can it be done?

In-depth analysis of EnableAutoConfiguration

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

Going back to the annotation of EnableAutoConfiguration, we find that its import is like this

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

But judging from the import annotation on EnableAutoCOnfiguration, it is not the introduction of another Configuration. It is an ImportSelector. What is this?

What is AutoConfigurationImportSelector?

The Enable annotation can not only implement the integration of multiple Configurations as simple as the previous case, but also implement some complex scenarios, such as different types of beans can be activated according to the context. The @Import annotation can configure three different classes.

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

Implement ImportBeanDefinitionRegistrar interface for dynamic injection

CacheService

public class CacheService {
}

LoggerService

public class LoggerService {
}

EnableDefineService

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented 
@Inherited  --允许被继承
@Import({GpDefineImportSelector.class})
public @interface EnableDefineService {

    String[] packages() default "";
}

GpDefineImportSelector

public class GpDefineImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        //获得指定注解的详细信息。我们可以根据注解中配置的属性来返回不同的class,
        //从而可以达到动态开启不同功能的目的
    
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 selector, it is very simple to analyze the principle of AutoConfigurationImportSelector in the follow-up. It is essentially a dynamic loading of beans.

The realization principle of @EnableAutoConfiguration annotation

After understanding ImportSelector and ImportBeanDefinitionRegistrar, the understanding of EnableAutoConfiguration is easier

It will import 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 implement dynamic bean-based loading. Earlier we talked about the working principle of Springboot @Enable* annotation. The array returned by the ImportSelector interface selectImports (the full class name of the class) will be included in the spring container.

Then 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;
   }
// 从配置文件(spring-autoconfigure-metadata.properties)中加载 AutoConfigurationMetadata
   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
         .loadMetadata(this.beanClassLoader);
// 获取所有候选配置类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;
   }
//获取元注解中的属性
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
//使用SpringFactoriesLoader 加载classpath路径下META-INF\spring.factories中,
//key= org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的value
   List<String> configurations = getCandidateConfigurations(annotationMetadata,
         attributes);
//去重
   configurations = removeDuplicates(configurations);
//应用exclusion属性
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
//过滤,检查候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载
   configurations = filter(configurations, autoConfigurationMetadata);
   //广播事件
fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

Essentially, EnableAutoConfiguration will help springboot applications load all configurations that conform to @Configuration into the current IoC container created by SpringBoot, and this uses the support of a tool class SpringFactoriesLoader provided by the Spring framework. And the conditional annotation @Conditional provided by Spring is used to selectively filter the beans that need to be loaded

SpringFactoriesLoader

In order to make up the basics for everyone, I will briefly analyze the use of the tool class SpringFactoriesLoader. It is actually the same principle as the SPI mechanism in java, but it is better than SPI in that it does not load all classes at once, but loads according to the key.

First of all, the function of SpringFactoriesLoader is to load the corresponding class into the spring IoC container according to the key from the classpath/META-INF/spring.factories file. Let's take everyone to practice

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 attributes in the dependency package through the following code

The operation 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, create a new spring.factories file under the folder, configure in the file, the key is the full path of the custom configuration class EnableAutoConfiguration, and the value is the full path of the configuration class

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

Repackage and re-run the SpringBootStudyApplication class.

It can be found that the class we wrote has been loaded.

The realization principle of @EnableAutoConfiguration annotation

After understanding ImportSelector and ImportBeanDefinitionRegistrar, the understanding of EnableAutoConfiguration is easier

It will import 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 implement dynamic bean-based loading. Earlier we talked about the working principle of Springboot @Enable* annotation. The array returned by the ImportSelector interface selectImports (the full class name of the class) will be included in the spring container.

Then 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;
   }
// 从配置文件(spring-autoconfigure-metadata.properties)中加载 AutoConfigurationMetadata 
   AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
         .loadMetadata(this.beanClassLoader);
// 获取所有候选配置类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;
   }
//获取元注解中的属性
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
//使用SpringFactoriesLoader 加载classpath路径下META-INF\spring.factories中,
//key= org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的value
   List<String> configurations = getCandidateConfigurations(annotationMetadata,
         attributes);
//去重
   configurations = removeDuplicates(configurations);
//应用exclusion属性
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
//过滤,检查候选配置类上的注解@ConditionalOnClass,如果要求的类不存在,则这个候选类会被过滤不被加载
   configurations = filter(configurations, autoConfigurationMetadata);
   //广播事件
fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

Essentially, EnableAutoConfiguration will help springboot applications load all configurations that conform to @Configuration into the current IoC container created by SpringBoot, and this uses the support of a tool class SpringFactoriesLoader provided by the Spring framework. And the conditional annotation @Conditional provided by Spring is used to selectively filter the beans that need to be loaded

Copyright statement: All articles in this blog, except for special statements, adopt the CC BY-NC-SA 4.0 license agreement. Please indicate the reprint from Mic takes you to learn architecture!
If this article is helpful to you, please help me to follow and like. Your persistence is the motivation for my continuous creation. Welcome to follow the WeChat public account of the same name for more technical dry goods!

跟着Mic学架构
810 声望1.1k 粉丝

《Spring Cloud Alibaba 微服务原理与实战》、《Java并发编程深度理解及实战》作者。 咕泡教育联合创始人,12年开发架构经验,对分布式微服务、高并发领域有非常丰富的实战经验。