SpringBoot启动流程浅析
方易明关注赞赏支持SpringBoot启动流程浅析
基于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编程框架WebFlux -
Servlet
基于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,p4application-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方法
推荐阅读更多精彩内容
- Spring基础(一) 本来是准备看一看Spring源码的。然后在知乎上看到来一个帖子,说有一群**自己连Spring官方文档都没有完全读... 此鱼不得水阅读 2,424评论 4 赞 10
- SpringBoot深入学习(一)-- refresh() springboot在启动的时候,会调用run方法,创建环境设置spring容器,其中包含refresh方法,... 未名枯草阅读 1,178评论 0 赞 6
- springboot启动的流程图 个人公众号(徐小耳博客)对于springboot,spring,tomcat,netty等框架有更深入的理解,请移... 简书徐小耳阅读 12,714评论 0 赞 100
- 最全面的 Spring 学习笔记 Spring致力于提供一种方法管理你的业务对象。在大量Java EE的应用中,随处可见Spring。今天我将简单的... JAVA架构师的圈子阅读 596评论 0 赞 16
- 深度使用Spring笔记 深入使用 Spring两种后处理器Bean 后处理器容器后处理器属性占位符配置器重写占位符配置器Spring 的自... 渐丶忘阅读 236评论 0 赞 1