阅读本文前,最好先阅读:Spring/SpringBoot系列之SpringBoot 源码常用注解【九】
1. SpringBoot 启动过程
在SpringBoot源码中以下四个接口的子类的方法经常被调用:
- ApplicationContextInitializer
- ApplicationRunner
- CommandLineRunner
- SpringApplicationRunListener
下面开始分析源码,先从 SpringBoot 的启动类的 run() 方法开始看,以下是调用链:
SpringApplication(primarySources)).run(args)分为两步:
- 创建 SpringApplication 对象
- 运行 run() 方法
1.1 创建 SpringApplication 对象
- 保存主配置类(这里是一个数组,说明可以有多个主配置类)
- 判断是否是一个 Web 应用
- 从类路径下找到 META-INF/Spring.factories 配置的所有 ApplicationContextInitializer,然后保存起来
- 从类路径下找到 META-INF/Spring.factories 配置的所有 ApplicationListener,然后保存起来
- 从多个配置类中找到有 main 方法的主配置类(只有一个)
比如spring-boot-2.2.5.RELEASE.jar下META-INF/Spring.factories内容如下
1.2 运行 run() 方法
- 创建计时器
- 声明 IOC 容器
- 从类路径下找到 META/INF/Spring.factories 获取 SpringApplicationRunListeners
- 回调所有 SpringApplicationRunListeners 的 starting() 方法
- 封装命令行参数
- 准备环境,包括创建环境,创建环境完成后回调 SpringApplicationRunListeners#environmentPrepared()方法,表示环境准备完成
- 打印 Banner
- 创建 IOC 容器(决定创建 web 的 IOC 容器还是普通的 IOC 容器)
- 准备上下文环境,将 environment 保存到 IOC 容器中,并且调用 applyInitializers() 方法。applyInitializers() 方法回调之前保存的所有的 ApplicationContextInitializer 的 initialize() 方法;然后回调所有的 SpringApplicationRunListener#contextPrepared() 方法 ;最后回调所有的 SpringApplicationRunListener#contextLoaded() 方法
- 刷新容器,IOC 容器初始化(如果是 Web 应用还会创建嵌入式的 Tomcat),扫描、创建、加载所有组件的地方
- 从 IOC 容器中获取所有的 ApplicationRunner 和 CommandLineRunner 进行回调
- 调用 所有 SpringApplicationRunListeners#started()方法
run() 阶段主要就是回调本节开头提到过的4种监听器中的方法与加载项目中组件到 IOC 容器中,而所有需要回调的监听器都是从类路径下的 META-INF/Spring.factories 中获取,从而达到启动前后的各种定制操作。
2. SpringBoot 自动配置原理
2.1 @SpringBootApplication
SpringBoot 项目的一切都要从 @SpringBootApplication 这个注解开始说起。
@SpringBootApplication 标注在某个类上,说明:
- 这个类是 SpringBoot 的主配置类。
- 运行这个类的 main 方法来启动 SpringBoot 应用。
该注解的定义如下:
可以看到 SpringBootApplication 注解是一个组合注解,其主要组合了三个注解:
- @SpringBootConfiguration:该注解表示这是一个 SpringBoot 的配置类,其实它就是一个 @Configuration 注解而已。
- @ComponentScan:开启组件扫描。
- @EnableAutoConfiguration:从名字就可以看出来,就是这个类开启自动配置的。
2.2 @EnableAutoConfiguration
注解定义:
2.2.1 @AutoConfigurationPackage
从字面意思理解就是自动配置包。点进去可以看到就是一个 @Import 注解:@Import({Registrar.class}),导入了一个 Registrar 的组件。
在 Registrar 类中的 registerBeanDefinitions 方法上打上断点,启动项目后可以看到返回了一个包名,该包名其实就是主配置类所在的包:
总结:
@AutoConfigurationPackage 注解就是将主配置类的所在包及下面所有子包里面的所有组件扫描到Spring容器中。所以说,默认情况下主配置类包及子包以外的组件,Spring 容器是扫描不到的。
2.2.2 @Import({AutoConfigurationImportSelector.class})
该注解给当前配置类导入另外的 N 个自动配置类。
配置类导入规则
AutoConfigurationImportSelector 的 selectImports()方法 就是用来返回需要导入的组件的全类名数组的。得到全类名数组的调用链:getAutoConfigurationEntry() -> getCandidateConfigurations() -> loadFactoryNames():
loadFactoryNames() 方法传入了 EnableAutoConfiguration.class 这个参数。
loadFactoryNames() 中关键的三步:
- 从当前项目的类路径中获取所有 META-INF/spring.factories 文件下的信息。
- 将上面获取到的文件中的信息封装成一个 Map 返回
- 从返回的 Map 中通过刚才传入的 EnableAutoConfiguration.class 参数,获取所有值。
META-INF/spring.factories
在很多第三方依赖中都会有这个文件,一般每导入一个第三方的依赖,除了本身的jar包以外,还会有一个 xxx-spring-boot-autoConfigure,这个就是第三方依赖自己编写的自动配置类。以 spring-boot-autocongigure 这个依赖来说:
可以看到 EnableAutoConfiguration 下面有很多类,这些就是我们项目进行自动配置的类。
总结:
将类路径下 META-INF/spring.factories 里面配置的所有 EnableAutoConfiguration 的值加入到 Spring 容器中。而这些bean依赖条件注解进行自动装配
HttpEncodingAutoConfiguration:
这么多的自动配置类,明显有很多是平常没有使用到的。以 比较简单典型的配置类HttpEncodingAutoConfiguration为例来看一个自动配置类是怎么工作的:
- @Configuration:标记为配置类。
- @EnableConfigurationProperties({HttpProperties.class}):启动指定类的ConfigurationProperties功能;将配置文件中对应的值和 HttpProperties 绑定起来;并把 HttpProperties 加入到 IOC 容器中。
- @ConditionalOnWebApplication:web应用下才生效
- @ConditionalOnClass:指定的类存在才生效
- @ConditionalOnProperty:主配置文件中存在指定的属性才生效
因为 @EnableConfigurationProperties({HttpProperties.class})把配置文件中的配置项与 HttpProperties 类绑定上了。
然后在 HttpEncodingAutoConfiguration 中又引用了 HttpProperties ,所以最后就能在 HttpEncodingAutoConfiguration 中使用配置文件中的值了。
最终通过 @Bean 和一些条件判断往容器中添加组件,实现自动配置。(该Bean中属性值是从 HttpProperties 中获取)
HttpProperties
HttpProperties 通过 @ConfigurationProperties 注解将配置文件与自身属性绑定。
所有在配置文件中能配置的属性都是在 xxxProperties 类中封装着;配置文件能配置什么就可以参照某个功能对应的这个属性类。
3. 总结
- SpringBoot启动会加载大量的自动配置类。
- 看看SpringBoot提供的自动配置类是否有我们需要的功能。
- 再来看这个自动配置类中到底配置了那些组件(只要有我们要用的组件,就不需要再配置)。
- 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性,然后可以在配置文件中指定这些属性的值。xxxAutoConfiguration:自动配置类给容器中添加组件;xxxProperties:封装配置文件中相关属性。
很多需要待加载的类都放在类路径下的META-INF/Spring.factories 文件下,而不是直接写死在代码中,这样做就可以很方便我们自己或者是第三方去扩展,我们也可以实现自己 starter,让SpringBoot 去加载。