基于SpringBoot 2.1.5
SpringBoot
在Spring Framework
的基础上增加的自动配置(约定优于配置)特性能够让开发人员更少的关注底层而带来更快的开发速度。但是由此带来的弊端是开发人员过于依赖完善的框架功能而没有去深入细节,只知其然而不知其所以然。由此记录一下最近在读的SpringBoot
的源码并记录一下SpringBoot
的启动流程
SpringBoot
的启动逻辑在SpringApplication
这个类中,通过构造一个SpringApplication
并调用run
方法启动SpringBoot
应用程序。SpringBoot
启动后的主要流程:
设置webApplicationType(web应用类型)
webApplicationType是启动流程中一个比较重要的属性,SpringBoot根据它的类型来创建Environment对象和应用上下文对象(ApplicationContext)准备应用上下文环境(Environment)
根据上一步推断的webApplicationType创建不同类型的Environment,并且将用户的profile文件读取到Environment中读取profile
创建并配置应用上下文对象(ApplicationContext)
根据webApplicationType创建不同实现的ApplicationContext刷新应用上下文对象(refresh)
AbstractApplicationContext
抽象类定义了上下文对象初始化核心流程,SpringBoot
以BeanFactoryPostProcessor
的方式实现包扫描、自动配置,将Bean预先加载成BeanDefinition
后并实例化后续处理
发布应用已启动事件并且调用容器中的Runner
一、设置应用类型
当前的web应用类型
(webApplicationType
)是在SpringApplication
的构造函数
中设置的,设置的逻辑在WebApplicationType.deduceFromClasspath
中:
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
可以看出SpringBoot将应用程序分为三种类型:
Reactive
Spring
团队推出的Reactor
编程模型的非阻塞异步Web编程框架WebFluxServlet
基于J2EE Servlet API
的编程模型,运行在Servlet
容器上None
非Web应用程序
通过类路径中是否存在WebFlux
中的Dispatcherhandler
,SpringMVC
中的DispatcherServlet
、Servlet
、ConfigurableWebApplicationContext
来推断Web应用程序类型
二、准备应用上下文环境(Environment)
Environment
是SpringFramework
中一个很重要的接口,用于存放应用程序的配置信息
PropertySource
(org.springframework.core.env.PropertySource
)是用来将一个对象以键值对的形式表示,Spring
将多种来源的属性键值对转换成PropertySource
来表示
//SpringApplication的prepareEnvironment方法
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();//创建环境对象
configureEnvironment(environment, applicationArguments.getSourceArgs());//配置环境对象;主要是根据命令行参数配置profile
listeners.environmentPrepared(environment);//发布应用环境已准备事件
bindToSpringApplication(environment);//绑定spring.main属性到SpringApplication对象中
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());//如果用户设置的spring.main.web-application-type和spring推断的类型不一致,则使用用户设置的类型,创建对应的环境对象
}
ConfigurationPropertySources.attach(environment);//添加一个名为configurationProperties的PropertySource
return environment;
}
在这一步,SpringApplication
做了:
创建
Environment
对象
在getOrCreateEnvironment
方法中,会根据之前推断的webApplicationType
(web程序类型)创建不同了实现的Environment
对象-
配置
Environment
对象- 应用程序如果有命令行参数,则在
Environment
中添加一个与这个命令行参数相关的PropertySource
- 根据命令行参数中
spring.profiles.active
属性配置Environment
对象中的activeProfile
- 应用程序如果有命令行参数,则在
-
发布
ApplicationEnvironmentPreparedEvent
(应用环境已准备)事件SpringApplication
发布完这个事件后,一个类型为ConfigFileApplicationListener
的监听器会监听这个事件,它会去读取用户设置的profile
文件(读取profile
的详细流程在下一步中) -
将
Environment
中的spring.main
属性绑定到SpringAppilcation
对象中在执行到这一步时,
Environment
中已经包含了用户设置的profile
文件属性 转换
Environment
对象的类型
在上一步中,如果用户使用spring.main.web-application-type
属性手动设置了应用程序的webApplicationType
并且用户设置的类型与SpringApplication
推断出来的不一致,则SpringApplication
会将环境对象转换成用户设置的webApplicationType
相关的类型
三、读取profile
在创建
Environment
对象前,SpringAppilcation
已经将当前类路径jar包下所有spring.factories
文件中的ApplicationListener
加载并实例化完毕。
ApplicationListener
:Spring Framework
中的监听器接口,用来监听应用程序发布的事件
监听器列表中有一个类型为ConfigFileApplicationListener
的监听器,当监听到ApplicationEnvironmentPreparedEvent
事件时,它会从所有spring.factories
中加载EnvironmentPostProcessor
(环境后处理器)并执行他们的postProcessEnvironment
方法(这个监听器本身也是一个环境后处理器,所以它也会执行自身的postProcessEnvironment
方法,在这个方法中加载了用户设置的profile
并以PropertySource
的形式添加到Environment
中)。
ConfigFileApplicationListener
最终会构造一个Loader
的内部类并调用Loader.load()
方法加载profile
;在Loader
的构造函数中,会去加载所有spring.factories
中的PropertySourceLoader
,SpringBoot
提供了两个PropertySourceLoader
:
-
PropertiesPropertySourceLoader
(用来加载properties、xml文件) -
YamlPropertySourceLoader
(用来加载yml、yaml文件)
Loader.load()
加载profile
的伪代码:
配置文件目录
如果用户设置了spring.config.location
属性(用","分隔开表示多个),则使用这个属性值作为配置文件目录;否则使用默认的目录(classpath:/,classpath:/config/,file:./,file:./config/
)和spring.config.additional-location
设置的并集作为配置文件目录配置文件名称
如果用户设置了spring.config.name
属性(用","分隔表示多个),则使用这个属性作为配置文件名称;否则使用application
作为配置文件名文件扩展名
//此时还没有加载profile,因此两个属性的值只能通过命令行参数读取到
let profiels = spring.profiles.active和spring.profiles.include设置的值
for(profile : profiles){//profiles是一个双向队列
for(目录 : 配置文件目录集合){
let 配置文件名集合 = 集合;
for(文件名 : 配置文件名集合){
for(loader : propertySourceLoader){
for(文件扩展名 : loader.文件扩展名){
load(profile,目录 + 文件名 + "-" + profile + "." + 文件扩展名);
}
}
}
}
}
在load(加载)时,如果从当前的`profile`中读取到了`spring.profiles.active`和`spring.profiles.include`属性,会把解析出来的profile放入profiles中
通过以目录 + 文件名 + "-" + profileName + "." + 文件扩展名
的组合方式加载profile
,并将profile
以PropertySource
的形式添加到Environment
中。
profile
属性的优先级问题:如果在多个profile
中设置了同一个名称的属性,profile属性生效的规则是怎样的?
同名的
profile
:根据文件后缀优先级 properties > xml > yml > yaml被引用的
profile
:例一个profile
中同时有spring.profiles.active
和spring.profiles.include
属性,则active
的优先级 >include
的优先级默认的
profile
优先级别最低-
如果把默认的
profile
当做第一级profile
,在第一级profile
中引用的profile
(使用spring.profiles.active
或者spring.profiles.include
引用)当做下一级的profile
,则下一级的profile
(可能多个)优先级高于前一级的profile
(一个),多个profile
整体的优先级为第一级的profile
优先级;例:
application.yml
内容:
spring.profiles.active=p1,p2
spring.profiles.include=p3,p4
application-p1.yml
内容:
spring.profiles.include=p5
则p2 > p1 > p4 > p3 > default
其中p5 > p1
,结果是p2 > p5 > p1 > p4 > p3 > default
四、创建并配置应用上下文对象
ApplicationContext
是Spring Framework
中最核心的接口,用来表示一个应用的上下文;功能包括事件发布、国际化等,同时它也是一个BeanFactory
SpringApplication
通过webApplicationType
的类型来创建不同的ApplicationContext
,以SERVLET
类型的webApplicationType
为例,SpringApplication
会创建类型为AnnotationConfigServletWebServerApplicationContext
的上下文对象;
SpringApplication
在prepareContext
方法中对上下文对象进行预配置,主要做了
-
执行所有
ApplicationContextInitializer
的initialize
方法
这些ApplicationContextInitializer
是在SpringApplication
中的构造函数
中加载的(通过读取spring.factories
加载) - 发布
ApplicationContextInitializedEvent
(上下文已初始化)事件 - 发布
ApplicationPreparedEvent
(上下文已准备)事件
五、刷新应用上下文对象
这里是
ApplicationContext
真正开始初始化容器和创建bean
的阶段,其中bean
的整个生命周期可以从这一步骤看出来;Spring Framework
中的所有ApplicationContext
实现都直接或间接继承自AbstracttApplicationContext
,它的refresh
方法描述了整个上下文的初始化逻辑
以AnnotationConfigServletWebServerApplicationContext
(当应用的webApplicationType为Servlet时使用)这个实现类为例
AbstractApplicationContext的refresh方法
synchronized (this.startupShutdownMonitor) {
prepareRefresh();//(1)
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();//(2)
prepareBeanFactory(beanFactory);//(3)
try {
postProcessBeanFactory(beanFactory);//(4)
invokeBeanFactoryPostProcessors(beanFactory);//(5)
registerBeanPostProcessors(beanFactory);//(6)
initMessageSource();//(7)
initApplicationEventMulticaster();//(8)
onRefresh();//(9)
registerListeners();//(10)
finishBeanFactoryInitialization(beanFactory);//(11)
finishRefresh();//(12)
}catch (BeansException ex) {
//。。。。。。
}
finally {
resetCommonCaches();
}
}
1. 准备更新上下文时的预备工作:
- 初始化
PropertySource
- 验证
Enrivonment
中必要的属性
2. 获取上下文的内部BeanFactory
内部BeanFactory
的实现类是DefaultListableBeanFactory
3. 对BeanFactory做些预备工作:
- 设置
BeanFactory
的Bean
类加载器、Bean
表达式解析器、属性编辑器注册表 - 添加类型为
ApplicationContextAwareProcessor
、ApplicationListenerDetector
的BeanPostProcessor
- 让
BeanFactory
在自动装配时忽略一些接口类型 - 注册可解析的依赖(自动装配时碰到这些类型直接注入,包括
BeanFactory
、ResourceLoader
、ApplicationEventPublisher
、ApplicationContext
) - 在
BeanFactory
中注册一些单例对象,包括environment
、systemProperties
、systemEnvironment
4. 对BeanFactory进行预处理
- 添加一个
WebApplicationContextServletContextAwareProcessor
的BeanPostProcessor
- 使
BeanFactory
自动装配时忽略ServletContextAware
接口 - 在
BeanFactory
中注册request
、session
两种scope - 注册可解析的依赖(自动装配时碰到这些类型可以注解注入,包括
ServletRequest
、ServletResponse
、HttpSession
、WebRequest
)
5. 执行容器中的BeanFactoryPostProcessor执行到这时容器已经注册了三个BeanFactoryPostProcessor,分别为
-
SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
ApplicationContexttInitializer
初始化时注册 -
ConfigurationWarningsApplicationContextInitializer#ConfigurationWarningsPostProcessor
ApplicationContexttInitializer
初始化时注册 -
ConfigFileApplicationListener$PropertySourceOrderingPostProcessor
ApplicationPreparedEvent
事件发布时由ConfigFileApplicationListener
注册
BeanDefinitionRegistryPostProcessor
是一种特殊的BeanFactoryPostProcessor
,可以对BeanDefinition
的注册表进行预处理
在
BeanFactory
中找到已注册的BeanFactoryPostProcessor
,执行其中类型为BeanDefinitionRegistryPostProcessor
的postProcessBeanDefinitionRegistry
方法-
循环从BeanFactory中获取
BeanDefinitionRegistryPostProcessor
(从BeanDefinition注册表中获取,和上一步的来源不一样);有一个ConfigurationClassPostProcessor
(ApplicationContext
的构造函数中注册的)
ConfigurationClassPostProcessor会执行SpringBoot的自动装配功能,将spring.factories中类型为EnableAutoConfiguration的类读取成BeanDefinition并过滤掉不满足条件的然后注册到BeanFactory中。详细步骤在下一章这一步骤会不断从BeanFactory中获取没有执行的BeanDefinitionRegistryPostProcessor并执行(可能用户会里面注册同类型的处理器)直到没有找到新的BeanDefinitionRegistryPostProcessor
包扫描、自动装配的功能都在ConfigurationClassPostProcessor中完成,执行完这一步后,所有Bean都会加载成BeanDefinition放入容器中
执行他们的
postProcessBeanFactory
方法对BeanFactory
进行后处理
6. 注册BeanPostProcessor
BeanPostProcessor:
Bean生命周期的钩子,允许用户对实例化后的Bean进行操作
从BeanFactory中获取所有
BeanPostProcessor
在
BeanFactory
中注册一个类型为BeanPostProcessorChecker的BeanPostProcessor
将所有
BeanPostProcessor
按照实现了PriorityOrdered
、Ordered
、没有实现排序接口
的顺序注册所有BeanPostProcessor
到BeanFactory
在
BeanFactory
中注册一个类型为ApplicationListenerDetector
的BeanPostProcessor
7. 初始化MessageSource(国际化相关)//忽略
8. 初始化容器事件广播器(用来发布事件)
- 构造了一个SimpleApplicationEventMulticaster当成默认的事件广播器
9. 初始化一些特殊的Bean,主要做了:
- 初始化ThemeSource(跟国际化相关的接口)
- 创建WebServer
10. 将所有监听器注册到前两步创建的事件广播器中
11. 结束BeanFactory的初始化工作(这一步主要用来将所有的单例BeanDefinition实例化)
从
BeanFactory
中获取所有的BeanDefinition
的beanName
并遍历对
Bean
执行所有已注册的InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation
方法(如果这个方法返回了一个Bean,Spring不会对这个Bean的属性进行注入,并且这个Bean的生命周期也会缺少几个步骤)
PS:只要其中有一个方法的返回值不为null,则会立即返回这个Bean,这个Bean的生命周期和正常的Bean不同(Spring对这个类型的BeanPostProcessor的注释是让它有机会能返回一个代理对象)实例化bean
对
Bean
执行所有MergedBeanDefinitionPostProcessor.prostProcessMergedBeanDefinition
(用来修改BeanDefinition的信息)-
对Bean属性进行填充(还有利用BeanPostProcessor对特殊Bean创建代理等暂时不讨论)
- 获取
BeanFactory
中所有InstantiationAwareBeanPostProcessor
,对Bean
执行postProcessAfterInstantiation
方法(通常,这个方法应该返回true,如果返回false,后续的postProcessAfterInstantiation
方法就不会执行了) - 同上,获取所有
InstantiationAwareBeanPostProcessor
,对每一个InstantiationAwareBeanPostProcessor
分两次调用
1.postProcessProperties
,如果返回null,则继续调用下一步-
postProcessPropertyValues
(返回的PropertyValues
是最终使用的PropertyValues
。如果这一步返回null,则不会执行后面的InstantiationAwareBeanPostProcessor
) - 如果上一步返回的
PropertyValues
有属性,则将属性应用到bean
上
-
- 获取
对实现了
BeanNameAware
、BeanClassLoaderAware
、BeanFactoryAware
的接口进行接口调用对
Bean
执行BeanPostProcessor.postProcessBeforeInitialization
方法对实现了
InitializingBean
的Bean
调用接口方法,然后调用init-method(可以是@PostConstruct标注的方法)对
Bean
执行PostProcessor.postProcessAfterInitialization
方法如果
Bean
是一个SmartInitializingSingleton
,则调用Bean
的afterSingletonsInstantiated
方法
- 对于
EnvironmentAware
、ResourceLoaderAware
、ApplicationEventPublisherAware
、MessageSourceAware
、AppilcationContextAware
等接口,是使用ApplicationContextAwareProcessor
这个BeanPostProcessor
(postProcessBeforeInitialization
方法)实现调用的- 步骤2中,如果
postProcessBeforeInstantiation
返回了一个bean,则立马会对bean执行步骤8的PosttProcessor.postProcessAfterInitialization方法
12.afterRefresh(上下文刷新完毕)
- 初始
LifecycleProcessor
(生命周期处理器),向BeanFactory
注册一个DefaultLifecycleProcessor
- 调用
LifecycleProcessor
的onrefresh
方法(找到所有已注册的SmartLifecycle
,根据isRunning
和isAutoStartup
的条件判断,执行SmartLifecycle
的start
方法) - 在
ServletWebServerApplicationContetxt
中,启动了WebServer
并发布了ServletWebServerInitializedEvent
事件
总结:
Spring容器中单例对象的生命周期
使用BeanFactoryPostProcessor对BeanFactory进行预处理
对未实例化的
Bean
调用InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation
方法实例化Bean(此时会调用Bean构造函数)
对
Bean
调用MergedBeanDefinitionPostProcessor.prostProcessMergedBeanDefinition
可用来修改BeanDefinition信息对
Bean
调用InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation
方法对
Bean
调用InstantiationAwareBeanPostProcessor.postProcessProperties
可用来修改Bean的属性配置对
Bean
调用InstantiationAwareBeanPostProcessor.postProcessPropertyValues
可用来修改Bean的属性配置对实现了
BeanNameAware、BeanClassLoaderAware、BeanFactoryAware
的Bean
进行接口调用注入属性对
Bean
调用BeanPostProcessor.postProcessBeforeInitialization
可对实例化的Bean
进行操作对实现了
InitializingBean
的Bean
调用接口方法,然后有初始化方法(可以是@PostConstruct标注的方法)的话调用初始化方法对
Bean
执行PostProcessor.postProcessAfterInitialization
方法可对实例化的Bean
进行操作如果
Bean
实现了SmartInitializingSingleton
接口,则调用Bean的afterSingletonsInstantiated
方法
SpringApplication
在refresh
方法调用结束后,在JVM上注册了一个shutdownHook
,JVM正常关闭时会调用,其中做了一些资源清理和调用Bean
的close
的方法工作(单例Bean生命周期的一部分)
六、后续处理
- 发布应用程序已启动(
ApplicationStartedEvent
)事件 - 在
BeanFactory
中获取所有ApplicationRunner
和CommandLineRunner
并调用他们的run
方法