开发者

一文彻底弄懂Spring Boot自动配置

目录
  • 什么是自动配置?
  • Spring 加载 Bean
    • @ComponentScan
    • @Import
      • 导入类
      • 导入 ImportSelector 接口的实现类
  • SpringBoot 原理分析
    • @EnableAutoConfiguration
      • @Import(AutoConfigurationImportSelector.class)
      • AutoConfigurationPackage
  • SpringBoot 自动配置流程
    • 总结

      什么是自动配置?

      Spring Boot 的自动配置:当 Spring 容器启动后,一些配置类、bean 对象等就自动存入 Ioc 容器中,而不再需要我们手动去声明,从而简化了程序开发过程,省去了繁琐的配置操作

      也就是说,Spring Boot 的自动配置,就是 SpinrgBoot 依赖 jar 包中的配置类以及 Bean 加载到 Spring Ioc 容器中的过程

      在本篇文章中,我们主要学习一下两个方面:

      1. Spring 如何将对象加载到 Spring Ioc 容器中

      2. SpringBoot 是如何进行实现的

      我们首先来看 Spring 是如何加载 Bean 的

      Spring 加载 Bean

      当我们在项目中引入第三方的包时,其实就是在该项目下引入第三方的代码,我们通过在该项目下创建不同的目录来模拟第三方代码的引入:

      一文彻底弄懂Spring Boot自动配置

      当前项目目录为 com.example.springautoconfig,模拟第三方代码文件在 com.example.autoconfig 目录下 

      第三方文件代码:

      @Component
      public class AutoConfig {
          public void test() {
              System.out.println("test...");
          }
      }

      获取 AutoConfig:

      @SpringBootTest
      class SpringAutoconfigApplicationTests {
          @Autowired
          private ApplicationContext context;
          @Test
          void contextLoads() {
              AutoConfig bean = context.getBean(AutoConfig.class);
              System.out.println(bean);
          }
      }

      运行结果:

      一文彻底弄懂Spring Boot自动配置

      此时显示没有 com.example.autoconfig.AutoConfig 这个类型的 bean

      为什么会报错呢?

      Spring 使用 类注解(@Controller、@Service、@Repository、@Component、@Configuration) 和 @Bean 注解 帮助我们将 Bean 加载到 Spring Ioc 容器中时,有一个前提:这些注解需要和 SpringBoot 启动类(@SpringBootApplication 标注的类)在同一个目录下

      而在上述项目中,启动类所在目录为  com.example.springautoconfig,而 AutoConfig 类位于 com.example.autoconfig 目录下,因此,SpringBoot 并没有扫描到

      可是,当我们引入第三方的 jar 包时,第三方的 jar 代码目录也不在启动类的目录下,那么,如何让 Spring 帮我们管理这些 Bean 的呢?

      我们可以通过指定路径或引入的文件告诉 Spring,让 Spring 进行扫描

      常见的实现方法有两种:

      1. @ComponentScan 组件扫描

      2. @Import 导入

      @ComponentScan

      使用 @ComponentScan 注解,指定 Spring 扫描路径:

      @SpringBootApplication
      @ComponentScan("com.example.autoconfig")
      public class SpringAutoconfigApplication {
          public static void main(String[] args) {
              SpringApplication.run(SpringAutoconfigApplication.class, args);
          }
      }

      运行程序并观察结果:

      一文彻底弄懂Spring Boot自动配置

       成功获取到 AutoConfig bean

      那么,Spring Boot 是否使用了这种方式呢?

      显然没有。若 Spring Boot 采用这种方式,当我们引入大量的第三方依赖,如 MyBATis、jackson 等时,就需要在启动类上配置不同依赖需要扫描的包,非常繁琐

      @Import

      @Import 导入主要有以下几种形式:

      1. 导入类

      2. 导入 ImportSelector 接口的实现类

      导入类

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

      运行程序,观察结果:

      一文彻底弄懂Spring Boot自动配置

      成功获取到 AutoConfig Bean

      若在文件中有多个配置项:

      @Component
      public class AutoConfig2 {
          public void test() {
              System.out.println("test...");
          }
      }

      此时就需要导入多个类:

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

      显而易见,这种方式也比较繁琐,因此,Spring Boot 也没有采用

      导入 ImportSelector 接口的实现类

      实现 ImportSelector 接口:

      public class MyImportSelector implements Impor编程客栈tSelector {
          @Override
          public String[] selectImports(AnnotationMetadata importingClassMetadata) {
              // 返回需要导入的全限定类名
              return new String[]{"com.example.autoconfig.AutoConfig", "com.example.autoconfig.AutoConfig2"};
          }
      }

      导入:

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

      运行程序,并观察结果:

      一文彻底弄懂Spring Boot自动配置

      这种方式也可以导入第三方依赖提供的 Bean

      但是,它们都有一个明显的问题:使用者需要知道第三方依赖中有哪些 Bean 对象或配置类,若我们在导入过程中漏掉了一些 Bean,就可能会导致我们的项目出现问题

      依赖中有哪些 Bean,使用时需要配置哪些 Bean,这些问题第三方依赖最为清楚,那么,能否由第三方依赖来做这些事情呢?

      比较常见的方法是第三方依赖提供python一个注解,而这个注解一般是以 @EnableXxx 开头的注解,而注解中封装的就是 @Import 注解

      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.TYPE)
      // 指定要导入哪些类
      @Import(编程客栈MyImportSelector.class)
      public @interface EnableAutoConfig {
      }

      在启动类上使用第三方提供的注解:

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

      运行并观察结果:

      一文彻底弄懂Spring Boot自动配置

      可以看到,这种方式也可以导入第三方依赖提供的 Bean,且这种方式不需要使用方知道第三方依赖中有哪些 Bean 对象或配置类,而在 Spring Boot 中也是使用的这种方式

      SpringBoot 原理分析

      SpringBoot 是如何进行实现的?让我们从 SpringBoot 的启动类开始看:

      由 @SpringBootApplication 标注的类就是 SpringBoot 项目的启动类:

      一文彻底弄懂Spring Boot自动配置

      这个类与普通类的唯一区别就是 @SpringBootApplication 注解,这个注解也是 SpringBoot 实现自动配置的核心

      一文彻底弄懂Spring Boot自动配置

       @SpringBootApplication 是一个组合注解,注解中包含了:

      1. 元注解:

       JDK 中提供了 4 个 标准的用来对注解类型进行注解的注解类,称之为 meta-annotation(元注解)

      分别为:

      @Retention:描述注解保留的时间范围

      @Target:描述注解的使用范围

      @Documented:描述在使用 Javadoc 工具为类生成帮助文档时是否保留其注解信息

      @Inherited:使被其修饰的注解具有继承性(若某个类使用了 @Inherited,则其子类将自动具有该注解)

      2. @SpringBootConfiguration: 

      一文彻底弄懂Spring Boot自动配置

      标识当前类是一个配置类,里面其实就是 @Configuration,只是做了进一步的封装

      其中,@Indexed 注解是用来加速应用启动的

      3. @EnableAutoConfiguration(开启自动配置)

      是 spiring 自动配置的核心注解,我们在后续详细理解

      4.  ComponentScan(包扫描)

      可以通过 basePackageClasses basePackages 来定义要扫描的特定包,若没有定义特定的包,将从声明该注解的类的包开始扫描,这也是 SpringBoot 项目声明的注解类为什么必须在启动类目录下

      一文彻底弄懂Spring Boot自动配置

      也可以自定义过滤器,用于排查一些类、注解等

      接下来,我们重点来看  @EnableAutoConfiguration(开启自动配置)

      @EnableAutoConfiguration

      一文彻底弄懂Spring Boot自动配置

       @EnableAutoConfiguration 中主要包含两部分:

      @Import(AutoConfigurationImportSelector.class) 

      @AutoConfigurationPackage

      我们先来看  @Import(AutoConfigurationImportSelector.class) 

      @Import(AutoConfigurationImportSelector.class)

      使用 @Import 注解,导入了实现 ImportSelector 接口的实现类:

      一文彻底弄懂Spring Boot自动配置

      selectImports() 方法中,调用了 getAutoConfigurationEntry() 方法,获取可自动配置的配置类信息

      我们继续看  getAutoConfigurationEntry() 方法:

      一文彻底弄懂Spring Boot自动配置

      在  getAutoConfighttp://www.devze.comurationEntry() 方法中,主要通过 getCandidateConfigurations(annotationMetadata, attributes) 方法 和 fireAutoConfigurationImportEvents(configurations, exclusions) 方法,获取在配置文件中配置的所有自动配置类的集合

      我们先看  getCandidateConfigurations(annotationMetadata, attributes) 方法:

      一文彻底弄懂Spring Boot自动配置

      在 getCandidateConfigurations(annotationMetadata, attributes) 方法中,使用 ImportCandidates 进行加载

      那么,从哪里进行加载呢?

      从 断言 的错误信息中我们可以找到答案:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

      也就是说, getCandidateConfigurations 方法会获取所有基于META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中配置类的集合

      META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件:

      一文彻底弄懂Spring Boot自动配置

       其中包含了很多第三方依赖的配置文件 

      我们以 Redis 为例:

      一文彻底弄懂Spring Boot自动配置

      查看 RedisAutoConfiguration

      一文彻底弄懂Spring Boot自动配置

      可以看到,在使用 redis 时常用的 RedisTemplate 和 StringRedisTemplate 就位于其中,在我们需要使用时直接使用 @Autowired 进行注入就可以了

      但是,由于当前项目并没有引入 redis 相关 jar 包,此时这个类并不能被加载

      也就是说在加载自动配置类的时候,并不是将所有的配置全部加载进来,而是会先进行判断,根据 @ConditionalOnMissingBean@ConditionalOnSingleCandidate 等注解的判断进行动态加载

      即,在配置文件中使用 @Bean 声明对象,spring 会自动调用配置类中使用 @Bean 标识的方法,并将对象存放到 Spring Ioc 中,但是,在加载自动配置类的时候,并不是将所有的配置全部加载进来,而是会通过 @Conditional 等注解的判断进行动态加载@Conditional 是 spring 底层注解,会根据不同的条件,进行条件判断,若满足指定条件,配置类中的配置才会生效)

      我们继续看 fireAutoConfigurationImportEvents 方法,找到是从哪里获取配置类的:

      一文彻底弄懂Spring Boot自动配置

      可以看到,fireAutoConfigurationImportEvents 方法最终会从 META-INF/spring.factories 中获取配置类的集合

      我们来看 META-INF/spring.factories

      一文彻底弄懂Spring Boot自动配置

      META-INF/spring.factories 文件是 Spring 内部提供的一个约定俗称的加载方式,只需要在模块的 META-INF/spring.factories 文件中进行配置,Spring 就会把相应的实现类注入到 Spring 容器中

      例如,有一个自动配置类,希望 SpringB编程客栈oot 在启动时自动加载这个配置,我们就可以在 META-INF/spring.factories 文件按照指定格式进行配置,让 Spring Boot 应用启动时,读取这个 spring.factories 文件,根据文件中指定的配置类来进行自动配置

      spring 会加载所有 jar 包下的 META-INF/spring.factories 文件

      META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports   META-INF/spring.factories 都在引入的起步依赖中:

      一文彻底弄懂Spring Boot自动配置

      我们继续看  AutoConfigurationPackage

      AutoConfigurationPackage

      一文彻底弄懂Spring Boot自动配置

      在这个注解中,主要导入了一个配置文件 AutoConfigurationPackages.Registrar.class

      我们继续看 Registrar

      一文彻底弄懂Spring Boot自动配置

       Registrar 实现了 ImportBeanDefinitionRegistrar 接口,可以被 @Import 导入到 spring 容器中

      new PackageImports(metadata).getPackageNames().toArray(new String[0]): 当前启动类所在的包名

      也就是说,@AutoConfigurationPackage 的作用是 将启动类所在的包下面所有的组件都扫描注册到 spring 容器中

      最后,我们来总结一下 SpringBoot 自动配置的流程

      SpringBoot 自动配置流程

       SpringBoot 的自动配置的入口是  @SpringBootApplication 注解,在这个注解中,主要封装了 3 个注解:

      @SpringBootConfiguration:标识当前类是配置类

      @ComponentScan(包扫描):可以通过 basePackageClasses basePackages 定义要扫描的特定包,若没有定义特定的包,将从声明该注解的类的包开始扫描,也就是说,默认情况下扫描的是启动类所在的当前包以及子包

      @EnableAutoConfiguration(开启自动配置):主要包含两部分:

      @Import(AutoConfigurationImportSelector.class) :读取 META-INF/spring.factories 和  META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中定义的配置类​​​​

      @AutoConfigurationPackage:将启动类所在的包下所有组件都注入到 Spring 容器中

      总结

      到此这篇关于Spring Boot自动配置的文章就介绍到这了,更多相关Spring Boot自动配置内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

      0

      上一篇:

      下一篇:

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      最新开发

      开发排行榜