SpringBoot系列 - 自动装配

SpringBoot系列 - 自动装配

     概要

     自动装配:通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能

     我们现在提到自动装配的时候,一般会和 Spring Boot 联系在一起。但是,实际上 Spring Framework 早就实现了这个功能。Spring Boot 只是在其基础上,通过 SPI 的方式,做了进一步优化。

     SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的信息加载到 Spring 容器,并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装载进 SpringBoot。

     SpringBoot 最核心的功能就是自动装配。Starter 作为 SpringBoot 的核心功能之一,基于自动配置代码提供了自动配置模块及依赖的能力,让软件集成变得简单、易用。使用 SpringBoot 时,我们只需引入对应的 Starter,SpringBoot 启动时便会自动加载相关依赖,集成相关功能,这便是SpringBoot 的自动装配功能。 

     一、为什么需要Spring Boot 自动装配?

     没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦。但是,Spring Boot 中,我们直接引入一个 starter 即可。比如你想要在项目中使用 redis 的话,直接在项目中引入对应的 starter 即可。

1 <dependency>
2     <groupId>org.springframework.boot</groupId>
3     <artifactId>spring-boot-starter-data-redis</artifactId>
4 </dependency>

     引入 starter 之后,我们通过少量注解和一些简单的配置就能使用第三方组件提供的功能了。

     二、Spring自动装配的原理

     1.  原理的简单概括

     由@SpringBootAppliction组合注解中的@EnableAutoConfiguration注解开启自动配置,加载 spring.factories 文件中注册的各种 AutoConfiguration 配置类,当其 @Conditional 条件注解生效时,实例化该配置类中定义的 Bean,并注入 Spring 上下文。

     2. SpringBoot 自动装配过程涉及的主要内容

 1 @Target({ElementType.TYPE})
 2 @Retention(RetentionPolicy.RUNTIME)
 3 @Documented
 4 @Inherited
 5 @SpringBootConfiguration
 6 @EnableAutoConfiguration
 7 @ComponentScan(
 8     excludeFilters = {@Filter(
 9     type = FilterType.CUSTOM,
10     classes = {TypeExcludeFilter.class}
11 ), @Filter(
12     type = FilterType.CUSTOM,
13     classes = {AutoConfigurationExcludeFilter.class}
14 )}
15 )
16 public @interface SpringBootApplication {
17    //...
18 }

     1)@EnableAutoConfiguration

     扫描类路径下的META-INF/spring.factories文件,并加载其中中注册的 AutoConfiguration 配置类,开启自动装配;

     2)spring.factories

     配置文件,位于 jar 包的 META-INF 目录下,按照指定格式注册了 AutoConfiguration 配置类;

     自 Spring Boot 3.0 开始,自动配置包的路径从META-INF/spring.factories 修改为 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

     3)AutoConfiguration 类

     自动配置类,SpringBoot 的大量以 xxxAutoConfiguration 命名的自动配置类,定义了三方组件集成 Spring 所需初始化的 Bean 和条件;

     4)@Conditional 条件注解及其行生注解

     使用在 AutoConfiguration 类上,设置了配置类的实例化条件;

     5)Starter

     三方组件的依赖及配置,包括 SpringBoot 预置组件和自定义组件,往往会包含 spring.factories 文件、AutoConfiguration 类和其他配置类。

     其功能间的作用关系如下图:

   

    三、@EnableAutoConfiguration:实现自动装配的核心注解

    EnableAutoConfiguration 只是一个简单地注解,自动装配核心功能的实现实际是通过 AutoConfigurationImportSelector类。

 1 @Target({ElementType.TYPE})
 2 @Retention(RetentionPolicy.RUNTIME)
 3 @Documented
 4 @Inherited
 5 @AutoConfigurationPackage //作用:将main包下的所有组件注册到容器中
 6 @Import({AutoConfigurationImportSelector.class}) //加载自动装配类 xxxAutoconfiguration
 7 public @interface EnableAutoConfiguration {
 8     String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
 9 
10     Class<?>[] exclude() default {};
11 
12     String[] excludeName() default {};
13 }

   1. @Import

   @EnableAutoConfiguration的自动配置功能是通过@Import注解导入的ImportSelector来完成的。 这里我们看下@Import的基本使用方法。 

   @Import注解,提供了导入配置类的功能。SpringBoot 的源代码中,有大量的EnableXXX类都使用了该注解,了解@Import有助于我们理解 SpringBoot 的自动装配,@Import有以下三个用途:

  •  通过@Import引入@Configuration注解的类;
  •  导入实现了ImportSelector或ImportBeanDefinitionRegistrar的类;
  •  通过@lmport导入普通的POJO。 

   @Import的许多功能都需要借助接口ImportSelector来实现,ImportSelector决定可引人哪些 @Configuration的注解类,ImportSelector接口源码如下:

1 public interface ImportSelector {
2     String[] selectImports(AnnotationMetadata importingClassMetadata);
3 
4     @Nullable
5     default Predicate<String> getExclusionFilter() {
6         return null;
7     }
8 }

   ImportSelector接口只提供了一个参数为AnnotationMetadata的方法selectImports(),返回的结果为一个字符串数组。其中参数AnnotationMetadata内包含了被@Import注解的类的注解信息。在selectImports() 方法内可根据具体实现决定返回哪些配置类的全限定名,将结果以字符串数组的形式返回。

   2.  @AutoConfigurationImportSelector  加载自动装配类

   我们现在重点分析下AutoConfigurationImportSelector 类到底做了什么?

   AutoConfigurationImportSelector 是一个关键组件,它负责读取 spring.factories 文件中的自动配置类信息,并决定哪些自动配置类需要被加载

   先看一下加载的整体流程图:

   AutoConfigurationImportSelector类的继承体系如下:

 1 public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
 2 
 3 }
 4 
 5 public interface DeferredImportSelector extends ImportSelector {
 6 
 7 }
 8 
 9 public interface ImportSelector {
10     String[] selectImports(AnnotationMetadata var1);
11 }

      可以看出,AutoConfigurationImportSelector 类实现了 ImportSelector接口,也就实现了这个接口中的selectImports方法,该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中。

      AutoConfigurationImportSelector#selectImports:

 1 public String[] selectImports(AnnotationMetadata annotationMetadata) {
 2         // 1. 判断自动装配开关是否打开
 3         // 检查自动配置是否开启是通过读取环境变量 spring.boot.enableautoconfiguration 确定的,默认为 true
 4         if (!this.isEnabled(annotationMetadata)) {
 5             return NO_IMPORTS;
 6         } else {
 7            // 2.获取所有需要装配的bean
 8            // 返回封装后的自动配置信息
 9             AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
10             AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
11 
12             // 返回符合条件的配置类
13             return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
14         }
15 }

   这里我们需要重点关注一下getAutoConfigurationEntry()方法,这个方法主要负责加载自动配置类的。

   该方法调用链如下:

     

    现在我们结合getAutoConfigurationEntry()的源码来详细分析一下:

    AutoConfigurationImportSelector#getAutoConfigurationEntry:

 1 private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();
 2 
 3 AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
 4         // 1.判断自动装配开关是否打开。默认spring.boot.enableautoconfiguration=true,可在 application.properties 或 application.yml 中设置
 5         if (!this.isEnabled(annotationMetadata)) {
 6             return EMPTY_ENTRY;
 7         } else {
 8             // 2.用于获取EnableAutoConfiguration注解中的 exclude 和 excludeName。
 9             AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
10 
11             // 3.获取需要自动装配的所有配置类,读取META-INF/spring.factories
12             // 通过 SpringFactoriesLoader 类提供的方法加载类路径中 META-INF 目录下的 spring.factories 文件中针对 EnableAutoConfiguration 的注册配置类
13             List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
14 
15             // 4.对获得的注册配置类集合进行去重处理,防止多个项目引入同样的配置类
16             configurations = this.removeDuplicates(configurations);
17 
18             // 获得注解中被 exclude 或 excludeName 所排除的类的集合
19             Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
20 
21             // 检查被排除类是否可实例化,是否被自动注册配置所使用,不符合条件则抛出异常
22             this.checkExcludedClasses(configurations, exclusions);
23 
24             // 从自动配置类集合中去除被排除的类
25             configurations.removeAll(exclusions);
26 
27             // 检查配置类的注解是否符合 spring.factories 文件中 AutoConfigurationImportFilter 指定的注解检查条件
28             configurations = this.filter(configurations, autoConfigurationMetadata);
29 
30             // 将筛选完成的配置类和排查的配置类构建为事件类,并传入监听器。监听器的配置在于 spring.factories 文件中,通过 AutoConfigurationImportlistener 指定
31             this.fireAutoConfigurationImportEvents(configurations, exclusions);
32 
33             return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
34         }
35  }  

     getAutoConfigurationEntry()方法获取配置类的过程比较复杂,涉及到配置类的检查、去重、监听

     第2步:用于获取EnableAutoConfiguration注解中的 exclude 和 excludeName。   

     通过调试可以看到:

     第3步:获取需要自动装配的所有自动配置类,读取META-INF/spring.factories

     关键点在于getCandidateConfigurations方法中使用SpringFactoriesLoader加载META-INF/spring.factories文件中配置的EnableAutoConfiguration类型的自动配置类:

1 protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
2         List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
3         ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
4         Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
5         return configurations;
6     }

    通过调试可以看到:  

   第4步:spring.factories中这么多配置,每次启动都要全部加载么?    

   很明显,这是不现实的。我们 debug 到后面你会发现,configurations 的值变小了。   

   因为,这一步又经历了一遍筛选,@ConditionalOnXXX 中的所有条件都满足,该类才会生效。

   四、@Conditional 注解

   自动配置类通常会使用 @Conditional 注解来指定实例化条件。在实例化自动配置类之前,Spring Boot 会检查这些条件注解。如果条件满足,则实例化相应的自动配置类,并将其注册到 Spring 上下文中。

  @Conditional 注解允许用户根据特定的条件来动态配置 Spring Bean。换句话说,只有在特定条件满足的情况下,Spring 才会实例化和注入标注了 @Conditional 的 Bean。

  @Conditional 注解本身并不包含具体的条件逻辑,而是依赖于实现了 Condition 接口的类来定义条件逻辑。使用时需要将实现了 Condition 接口的类作为参数传递给 @Conditional 注解。

   1.  Condition 接口

1 package org.springframework.context.annotation;
2 
3 import org.springframework.core.type.AnnotatedTypeMetadata;
4 
5 @FunctionalInterface
6 public interface Condition {
7     // 决定条件是否匹配
8     boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
9 }

   matches方法的第一个参数为ConditionContext,可通过该接口提供的方法来获得 Spring 应用的上下文信息;matches方法的第二个参数为AnnotatedTypeMetadata,该接口提供了访问特定类或方法的注解功能,可以用来检查带有@Bean注解的方法上是否还有其他注解。

   2. conditional

1 @Target({ElementType.TYPE, ElementType.METHOD})
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 public @interface Conditional {
5     Class<? extends Condition>[] value();
6 }

  @Conditional 注解唯一的元素属性value是接口 Condition 的数组,只有在数组中指定的所有Conditionmatches方法都返回true的情况下,被注解的类才会被加载

  3. @Conditional 的衍生注解

  Spring 提供了一些常用的 @Conditional 注解,用于处理常见的条件逻辑:

  •   @ConditionalOnProperty: 根据配置属性来判断是否注入 Bean。
  •   @ConditionalOnClass: 当类路径中存在特定的类时注入 Bean。
  •   @ConditionalOnMissingBean: 当容器中不存在特定的 Bean 时注入。
  •   @ConditionalOnBean: 当容器中存在特定的 Bean 时注入。
  •   @ConditionalOnWebApplication: 只有在 Web 应用环境中才注入 Bean。
  •   @ConditionalOnExpression: 根据 SpEL 表达式的结果来判断是否注入 Bean。

   通过一个举例,我们看下衍生注解是如何发挥作用的。

   1)spring.factories文件

   我们在spring.factories文件中看到这一行自动配置类:

   

   2)SpringApplicationAdminJmxAutoConfiguration实现类

   其中有一个SpringApplicationAdminJmxAutoConfiguration实现类

1 # Auto Configure
2 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
3 org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
4 org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
5 org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
6 org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
7 org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\

    找到SpringApplicationAdminJmxAutoConfiguration这个类的源码:

 1 @AutoConfiguration(
 2     after = {JmxAutoConfiguration.class}
 3 )
 4 @ConditionalOnProperty(
 5     prefix = "spring.application.admin",
 6     value = {"enabled"},
 7     havingValue = "true",
 8     matchIfMissing = false
 9 )
10 public class SpringApplicationAdminJmxAutoConfiguration {
11     
12     //...
13     
14     public SpringApplicationAdminJmxAutoConfiguration() {
15     }
16 
17     //...
18 }    

   我们来看下 @ConditionalOnProperty 这个注解中各个属性的作用

 1 @Retention(RetentionPolicy.RUNTIME)
 2 @Target({ElementType.TYPE, ElementType.METHOD})
 3 @Documented
 4 @Conditional({OnPropertyCondition.class})
 5 public @interface ConditionalOnProperty {
 6     String[] value() default {};
 7 
 8     String prefix() default "";
 9 
10     String[] name() default {};
11 
12     String havingValue() default "";
13 
14     boolean matchIfMissing() default false;
15 }

   1) prefix  指定了属性的前缀

   比如:prefix = "spring.application.admin",它表示所有相关属性都应该以 spring.application.admin 开头。

   例如,这里的完整属性名是 spring.application.admin.enabled。

   2) value  指定了具体的属性名

   value = {"enabled"},在这个例子中,它是 enabled。

   与前缀结合,这个属性的完整名是 spring.application.admin.enabled。

   3. havingValue  指定了配置属性的期望值

   havingValue = "true",只有当 spring.application.admin.enabled 的值为 true 时,条件才会满足。

   例如,只有在 application.properties 或 application.yml 文件中配置了 spring.application.admin.enabled=true 时,注解标注的配置才会生效。

   4.  matchIfMissing  指定了当配置属性缺失时是否默认匹配

   matchIfMissing = false

   如果 matchIfMissing 设置为 true,即使配置文件中没有 spring.application.admin.enabled 属性,条件也会匹配。

   如果 matchIfMissing 设置为 false(如本例), 当配置文件中没有 spring.application.admin.enabled 属性时,条件不会匹配。

   五、自定义starter

   现在我们就来写一个 starter,实现自定义线程池

   1. 第一步,创建threadpool-spring-boot-starter工程

     

   2. 第二步,引入 Spring Boot 相关依赖 

 1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 2          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 3     <modelVersion>4.0.0</modelVersion>
 4     <groupId>org.example</groupId>
 5     <artifactId>threadpool-spring-boot-starter</artifactId>
 6     <parent>
 7         <groupId>org.example</groupId>
 8         <artifactId>springboot</artifactId>
 9         <version>1.0-SNAPSHOT</version>
10         <relativePath/>
11     </parent>
12     <packaging>pom</packaging>
13     <version>1.0-SNAPSHOT</version>
14     <dependencies>
15         <!--引入Spring Boot Starter基础库-->
16         <dependency>
17             <groupId>org.springframework.boot</groupId>
18             <artifactId>spring-boot-starter</artifactId>
19         </dependency>
20     </dependencies>
21 </project>

   3. 第三步,创建ThreadPoolAutoConfiguration

 1 @Configuration
 2 public class ThreadPoolAutoConfiguration {
 3 
 4     /**
 5      * 需要项目中存在ThreadPoolExecutor类。该类为JDK自带,所以一定成立。
 6      * @return
 7      */
 8     @Bean
 9     @ConditionalOnClass(ThreadPoolExecutor.class)
10     public ThreadPoolExecutor MyThreadPool() {
11         return new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
12     }
13 }

   4. 第四步,在threadpool-spring-boot-starter工程的 resources 包下创建META-INF/spring.factories文件

   5. 第五步,新建工程引入threadpool-spring-boot-starter

 1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 2          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 3     <modelVersion>4.0.0</modelVersion>
 4     <groupId>org.example</groupId>
 5     <artifactId>springboot_10_define_test_starter</artifactId>
 6     <parent>
 7         <groupId>org.example</groupId>
 8         <artifactId>springboot</artifactId>
 9         <version>1.0-SNAPSHOT</version>
10         <relativePath/>
11     </parent>
12     <packaging>pom</packaging>
13     <version>1.0-SNAPSHOT</version>
14     <dependencies>
15         <!--引入Spring Boot Starter基础库-->
16         <dependency>
17             <groupId>org.springframework.boot</groupId>
18             <artifactId>spring-boot-starter</artifactId>
19         </dependency>
20         <dependency>
21             <groupId>org.springframework.boot</groupId>
22             <artifactId>spring-boot-starter-test</artifactId>
23         </dependency>
24 
25         <dependency>
26             <groupId>org.example</groupId>
27             <artifactId>threadpool-spring-boot-starter</artifactId>
28         </dependency>
29     </dependencies>
30 </project>

   6. 测试类

 1 @SpringBootTest
 2 @RunWith(SpringRunner.class)
 3 public class Test1 {
 4 
 5     @Autowired
 6     private ThreadPoolExecutor myThreadPool;
 7 
 8     @Test
 9     public void test() {
10         System.out.println(myThreadPool.getCorePoolSize());
11     }
12 
13 }
14 
15 //测试结果
16 5 

   六、总结

   Spring Boot 通过@EnableAutoConfiguration开启自动装配,通过 SpringFactoriesLoader 最终加载META-INF/spring.factories中的自动配置类实现自动装配,这些配置类通常根据条件,通过@Conditional注解按需加载的,想要其生效必须引入spring-boot-starter-xxx包实现起步依赖。


   参考链接:

   https://juejin.cn/post/7137095650160115743

   https://javaguide.cn/system-design/framework/spring/spring-boot-auto-assembly-principles.html

posted @ 2024-08-07 20:34  欢乐豆123  阅读(31)  评论(0编辑  收藏  举报