SpringBoot2-配置与原理深入解析
一、SpringBoot自动配置原理
SpringBoot中的自动配置原理,需要从@SpringBootApplication
这个注解出发。
这个注解相当于以下三个注解:
@SpringBootConfiguration
:等价于@Configuration
,表明这是一个SpringBoot项目的配置类。@EnableAutoConfiguration
:开启自动配置功能。@ComponentScan
:自动扫描并加载给定的路径中符合条件的组件,注入到IOC容器中。
其中自动配置的原理,就和@EnableAutoConfiguration
有关。
下面着重对其进行分析。
@EnableAutoConfiguration
的源码:
1 |
|
可以看到,@EnableAutoConfiguration
是由@AutoConfigurationPackage
和@Import
两个注解组成的。
其中@AutoConfigurationPackage
会自动导入主程序所在包及其子包的一系列组件。即自动导入包机制。
@Import
主要是按需加载自动配置类,将生效的自动配置类加载到容器。
下面对这两个注解详细分析。
1.1 @AutoConfigurationPackage自动导包
@AutoConfigurationPackage
注解是用来自动导入包的,下面深入解析其是如何导入的。
进入其源码:
1 |
|
可以看到,AutoConfigurationPackage
为我们自动引入了一个注册器。继续进入AutoConfigurationPackages.Registrar
:
Registrar
是AutoConfigurationPackages
的一个内部类,其中有两个方法,主要关注第一个方法。
对registerBeanDefinitions()
这个方法进行分析
metadata
参数:注解的元信息。这里指的是@AutoConfigurationPackage
这个注解,而这个注解相当于直接配置在了启动类上,因此从元信息中获取包名,就是启动类所在的目录名。比如当前启动类在com/kang/root
目录下,则new PackageImports(metadata).getPackageNames()
的值为com.kang.root
。register()
:将指定目录下的包的所有组件注册进来。
由此,我们可以得知,@AutoConfigurationPackage
注解,利用Registrar
给容器中导入一系列组件,将指定的一个包下的所有组件导入进来,这个包就是主程序所在的包。
因此我们可以解释,为什么SpringBoot扫描包的路径就是主程序所在包及其子包。
1.2 @Import 按需加载配置类
@EnableAutoConfiguration
第二个重要的注解是@Import
,其引入了AutoConfigurationImportSelector
,这个类的继承体系如下:
可以看到其实现了ImportSelector
接口,并且实现了这个接口的selectImports
方法,这个方法主要用于获取所有符合条件的全限定类名,这些类需要被加载到IOC容器中。
AutoConfigurationImportSelector
类:
1 |
|
接下来进入getAutoConfigurationEntry()
这个方法,查看是如何获取需要装配的bean的。
这个方法的源码如下:
1 |
|
代码解析如下:
1、判断自动装配开关是否打开,即spring.boot.enableautoconfiguration
的值是否为true.
2、获取@EnableAutoConfiguration
注解的所有属性,即exclude
和excludeName
。
3、getCandidateConfigurations()
方法,从META-INF/spring.factories
中获取所有需要自动装配的配置类。
内部调用
SpringFactoriesLoader.loadFactoryNames()
,然后调用loadSpringFactories()
方法。然后进入这个方法。可以看到,这个方法加载了
META-INF/spring.factories
这个配置文件,默认扫描当前系统里面所有的META-INF/spring.factories
位置的文件。当前版本一共131个需要自动装配的类,没有第三方starter时,他们都是来自
spring-boot-autoconfigure-2.5.2
包里的META-INF/spring.factories
文件。这个自动配置包里面规定了SpringBoot自动配置的一些属性,其中的EnableAutoConfiguration
属性就是所有要自动装配的类,如图。
因此,如果我们想要实现一个starter,就必须在META-INF/spring.factories
文件中配置这样的配置项,指定要加载的自动配置包。
这一步中将自动配置类的所有需要加载的包都读取过来了,那么这些包必须全部加载吗?答案是否定的。
顺着源码继续往下。
4、按需开启自动配置项。虽然读取了所有需要自动加载的类,但是这些类生效都是有条件的,比如需要导入相关的包,这个类才能被加载。因此最终只加载生效的部分类。
配置类中使用@Condition条件装配,判断当前类是否生效。
1.3 总结
关于自动配置:
- SpringBoot先读取所有的自动配置类:
xxxxxAutoConfiguration
- 每个自动配置类按照条件生效,默认都会绑定
xxxxProperties
类,这个类里面设置了默认的值。 xxxProperties
类和配置文件进行了绑定,可以通过配置文件对默认值进行设置。生效的配置类就会给容器中装配很多组件。只要容器中有这些组件,相当于这些功能就有了
如果我们需要的包没有被starter,则需要手动导入。
我们可以在配置文件中对配置项的值进行修改。
二、Profile功能
为了方便多环境适配,SpringBoot简化了profile功能。
我们可以配置多套环境,比如测试环境和生产环境,可以方便的在不同的环境中切换。
比如同时配置多个配置文件,表示多个配置环境,根据需求使用不同的配置:
1 |
|
2.1 application-profile功能
- 默认配置文件
application.yaml
任何时候都会加载 指定环境配置文件,格式: application-{env}.yaml,比如application-prod.yaml文件,代表
prod
环境。激活指定环境:
方式一:使用配置文件激活。比如在默认配置文件中使用
spring.profiles.active=test
激活test环境。方式二:命令行激活。对于已经打包的程序,命令行也能够指定环境并修改配置文件的任意值。比如使用命令行激活prod环境,并指定参数值
1
2# 这时,命令行的参数值会生效
java -jar xxx.jar --spring.profiles.active=prod --person.name=haha
默认配置与环境配置同时生效
- 对于同名配置项,指定的环境配置生效。
2.2 @Profile条件装配功能
@Profile可以用在类上或者方法上,表示只有在指定的环境下才生效。
比如@Profile("test")
标注,表示这个类/方法只有在test环境下才生效。
比如:
person.java
1 |
|
Boss.java
1 |
|
Worker.java
1 |
|
然后定义一个controller
类,HelloController.java
1 |
|
这样,如果在test
环境下,访问主页就会返回com.kang.boot.bean.Worker
,如果在prob
环境下,返回值为com.kang.boot.bean.Boss
。
2.3 profile分组
可以使用spring.profiles.group
对配置文件进行分组,然后同时激活同一个组的多个配置文件。比如:
application.properties
1 |
|
三、配置加载优先级
参考官方文档Externalized Configuration
后加载的会覆盖前面加载的。
根据官方文档,SpringBoot的配置文件可以来自于以下方面,按照从上往下的顺序,其中后加载的会覆盖前面的:
- Default properties (specified by setting
SpringApplication.setDefaultProperties
). @PropertySource
annotations on your@Configuration
classes. Please note that such property sources are not added to theEnvironment
until the application context is being refreshed. This is too late to configure certain properties such aslogging.*
andspring.main.*
which are read before refresh begins.- Config data (such as
application.properties
files) - A
RandomValuePropertySource
that has properties only inrandom.*
. - OS environment variables.
- Java System properties (
System.getProperties()
). - JNDI attributes from
java:comp/env
. ServletContext
init parameters.ServletConfig
init parameters.- Properties from
SPRING_APPLICATION_JSON
(inline JSON embedded in an environment variable or system property). - Command line arguments.
properties
attribute on your tests. Available on@SpringBootTest
and the test annotations for testing a particular slice of your application.@TestPropertySource
annotations on your tests.- Devtools global settings properties in the
$HOME/.config/spring-boot
directory when devtools is active.
加粗是比较常用的两种。
3.1 外部配置源
常用:Java属性文件、YAML文件、环境变量、命令行参数;
3.2 配置文件查找位置
配置文件的查找顺序:
(1) classpath 根路径(Java文件夹和resource文件夹都属于根路径,编译后都位于target根目录下)
(2) classpath 根路径下config目录
(3) jar包当前目录
(4) jar包当前目录的config目录
(5) /config子目录的直接子目录
3.3 配置文件加载顺序:
- 当前jar包内部的application.properties和application.yml
当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
引用的外部jar包的application.properties和application.yml
- 引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml
3.4 总结
指定环境优先,外部优先,后面的可以覆盖前面的同名配置项。
四、自定义starter
4.1 starter启动原理
starter的启动流程:
1、指定要加载的自动配置类
在引入starter后,会加载指定的自动配置类。
SpringBoot的autoconfigure
包中,META-INF
下的spring.factories
文件,使用了 EnableAutoConfiguration
的值指定了项目启动时要加载的自动配置类。如图:
2、自动配置类中指定要加载的类xxxProperties
一般来说,自动配置类用于自动注册组件,并使用指定的配置类,进行属性值的注入。
以org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
为例,加载这个自动配置类,这个配置类的声明如下:
1 |
|
这个自动配置类使用@EnableConfigurationProperties
注解,指定了要加载的配置类CacheProperties
。
其余的注解则规定了这个自动配置类的生效条件,比如@ConditionalOnBean
、@ConditionalOnMissingBean
等注解。例如:@ConditionalOnBean(CacheAspectSupport.class)
表示当前容器中有CacheAspectSupport
这个类的组件时,当前的自动配置类才生效。
3、xxxProperties中绑定配置文件中的配置属性
这个类中规定了默认值,自动配置组件的值就是从这里取。并且这个类和配置文件绑定,我们可以通过配置文件对默认值进行修改。
1 |
|
比如CacheProperties
这个配置类,使用@ConfigurationProperties(prefix = "spring.cache")
注解,将配置文件中spring.cache
开头的配置项的值,绑定到当前类中对应的属性。
这样,我们在总配置文件中使用spring.cache.xxx=xxx
,就可以对cache相关的内容进行配置。
4.2 自定义starter
仿照官方的starter启动流程,我们可以创建自定义的starter。
1、创建starter和starter-autoconfigure项目
自定义starter需要创建一个starter项目和一个对这个starter自动配置的项目。
如图:
自定义的启动器一般是xxx-spring-boot-starter的命名格式,对其自动配置的包一般是xxx-spring-boot-starter-autoconfigure格式。
在启动器中引入自动配置包的依赖,如图:
这样,我们使用的时候,只需要引入starter就可以使用,它就会自动为我们导入自动配置的依赖包。
我们可以把需要的配置,都在自动配置包中引入依赖。
2、在spring.factories
中指定要加载的自动配置类
为了保证我们的自动配置类能自动加载,需要在自动配置包的META-INF/spring.factories
文件中,指定项目启动时要加载的自动配置类:
spring.factories
中的内容如下:
1 |
|
指定项目启动时,会加载com.kang.hello.auto.HelloServiceAutoConfiguration
这个配置类。
3、编写xxxAutoConfiguration和xxxProperties和功能类
配置类HelloProperties
,对属性值和配置文件中的值进行绑定
1 |
|
这样,项目中在配置文件中使用kang.hello
就可以对prefix
和suffix
进行配置。
自动配置类HelloServiceAutoConfiguration
,用于注册组件:
1 |
|
当容器中没有HelloService
组件时,自动配置类才会注册HelloService
组件,并根据HelloProperties
中的绑定的值,对HelloProperties
中的属性值进行注入。
HelloService
中定义了自定义启动器要封装的功能方法。
1 |
|
4、将 starter和starter-autoconfigure项目进行打包安装
将自动配置包使用Maven进行install,然后对starter进行install。
这样,我们的自定义启动器就算创建好了,别的项目直接引入自定义的starter依赖就可以使用其中提供的方法了。
5、测试自定义starter
新建一个项目,引入自定义的starter依赖:
pom.xml
1 |
|
然后在配置类中,使用kang.hello
对两个参数进行配置
application.properties
1 |
|
编写方法,调用starter中定义好的方法完成功能:
HelloController
1 |
|
这样,在浏览器中发送hello
请求,返回值为:
1 |
|
测试成功,说明自定义的启动器没有问题。
上面使用的是自动装配的HelloService
,如果我们不希望使用提供的原方法,就可以自定义HelloService
,这样,自动配置类不会自动注册HelloService
,项目会使用我们自己的HelloService
。实现了SpringBoot中的自定义优先的情况。
因此,如果我们在使用starter的过程中,如果现有的方法不能满足我们的需求,我们就可以根据源码找到其方法进行重写,这样项目就会使用我们重写之后的方法。
比如我们定义一个配置类,并自己新建一个HelloService
对象,通过调用方法实现需求,这时底层使用的就是我们创建的这个对象:
1 |
|
五、SpringBoot启动原理
5.1 SpringBoot启动过程
进入run
方法:
可以看到主要有两步,创建SpringApplication
和运行SpringApplication
.
1、创建SpringApplication
进入new SpringApplication
方法,创建SpringApplication
,应用创建的过程,简单来说就是先将一些组件读取到应用中。
SpringApplication.java
的构造器方法:
1 |
|
主要步骤总结如下:
- 用于保存一些信息。
判定当前应用的类型。内部使用了ClassUtils工具类。判断结果为Servlet类型
bootstrapRegistryInitializers
:初始启动引导器注册初始化,其内部通过SpringFactoriesLoader
去spring.factories
文件中找org.springframework.boot.Bootstrapper
,即查看是否有初始的启动引导器。- 去
spring.factories
文件找ApplicationContextInitializer
,即ApplicationContext
初始化器- 结果保存到:List
> initializers
- 结果保存到:List
- 去
spring.factories
找ApplicationListener
,即应用监听器- 将结果保存到:List
> listeners
- 将结果保存到:List
- 查找主程序,找到第一个main方法就是主程序。
2、运行 SpringApplication
创建完SpringApplication
之后,执行run()
方法。
SpringApplication.java
的run()
方法:
1 |
|
run()
方法的主要步骤如下:
创建
StopWatch
对象并启动,其记录了应用的启动时间。创建引导上下文(Context环境)—-
createBootstrapContext()
- 获取到所有之前的 bootstrappers, 依次执行 intitialize() ,来完成对引导启动器上下文环境设置
让当前应用进入
headless
模式,即java.awt.headless
(自力更生模式)获取所有
RunListener
(运行监听器),为了方便所有Listener进行事件感知。- 去
spring.factories
文件中找SpringApplicationRunListener
, 比如EventPublishRunListenr
- 去
遍历所有的
SpringApplicationRunListener
调用其starting()
方法;相当于通知所有感兴趣系统正在启动过程的监听器,项目正在starting
。保存命令行参数—-
ApplicationArguments
准备环境—-
prepareEnvironment()
;- 返回或者创建基础环境信息对象—-
StandardServletEnvironment
- 配置环境信息对象。
- 读取所有的配置源的配置属性值。
- 绑定环境信息
- 监听器调用
environmentPrepared()
,通知所有的监听器当前环境准备完成。
- 返回或者创建基础环境信息对象—-
创建IOC容器 —-
createApplicationContext()
根据项目类型创建容器。
1
2
3
4
5
6
7
8
9
10try {
switch (webApplicationType) {
case SERVLET:
return new AnnotationConfigServletWebServerApplicationContext();
case REACTIVE:
return new AnnotationConfigReactiveWebServerApplicationContext();
default:
return new AnnotationConfigApplicationContext();
}
}比如当前类型为servlet,则创建
AnnotationConfigServletWebServerApplicationContext
准备 IOC容器的基本信息 —-
prepareContext()
- 保存环境信息
- IOC容器的后置处理流程。
- 应用初始化器—
applyInitializers
;- 遍历所有的
ApplicationContextInitializer
。就是创建SpringApplication
时找到的那些初始化器。然后调用它们的initialize
方法,来对ioc容器进行初始化扩展功能。
- 遍历所有的
- 遍历所有的 listener,即第一步中查找到的监听器,内部调用每个监听器的
contextPrepared()
,表示IOC容器准备完成。 - 这个方法最后,所有的监听器调用
contextLoaded()
,即通知所有的监听器IOC容器加载完毕。
刷新IOC容器。refreshContext
- 创建容器中的所有组件(即Spring的运行原理)
容器刷新完成后工作 —-
afterRefresh()
调用
started
方法,调用所有监听器的started
方法,即通知所有监听器start结束。调用所有runners —-
callRunners()
- 获取容器中的
ApplicationRunner
- 获取容器中的
CommandLineRunner
- 合并所有runner并且按照@Order进行排序
- 遍历所有的runner,调用 run方法
- 获取容器中的
如果以上有异常,调用Listener 的
failed()
方法,表示启动失败了。调用所有监听器的
running
方法 —-listeners.running(context);
,通知所有的监听器,容器正在运行。- 如果
running
有问题,继续通知failed()
。调用所有 Listener 的failed()
,通知所有的监听器。
- 如果
5.2 Application Events and Listeners
SpringApplication有独特的事件和监听器,根据SpringBoot的启动过程,我们可以使用他们在启动过程中做一些额外的事情:
- ApplicationContextInitializer
- ApplicationListener
- SpringApplicationRunListener:在IOC容器的各个阶段做一些事情,一共有七种状态,表示IOC容器的不同阶段,不同阶段的容器具备的功能也是不同的。根据这个特征可以定制化一些功能。
实现代码举例:
MyApplicationContextInitializer
1 |
|
MyApplicationListener
1 |
|
MySpringApplicationRunListener
1 |
|
此外,由于ApplicationContextInitializer
,ApplicationListener
,SpringApplicationRunListener
都需要在spring.factories
文件中找,因此我们需要在这个文件中配置他们:
resources/META-INF/spring.factories
1 |
|
5.3 ApplicationRunner和CommandLineRunner
ApplicationRunner
和CommandLineRunner
都是在IOC容器中获取的,因此我们需要将它们注册为组件。
MyApplicationRunner
1 |
|
MyCommandLineRunner
1 |
|
这样,启动主程序,可以看到运行结果,和我们分析的SpringBoot启动过程是一样的:
1 |
|
- 本文作者:Kangshitao
- 本文链接:http://kangshitao.github.io/2021/07/12/springboot-advance/index.html
- 版权声明:本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!