imp - SpringBoot启动 & IOC初始化 & 监听机制 · SpringBoot3
SpringBoot 启动流程
new SpringApplication()
- 确认 web 应用的类型
- 加载 ApplicationContextInitializer
- 加载 ApplicationListener
- 记录主启动类
run()
- 准备环境对象 Environment,用于加载系统属性等
- 打印 Banner
- 实例化容器 Context
- 准备容器,为容器设置 Environment、BeanFactoryPostProcessor,并加载主类对应的 BeanDefinition
- 刷新容器(创建 Bean 实例)
- 返回容器
总结
代码块收起展开
总:SpringBoot启动,其本质就是加载各种配置信息,然后初始化IOC容器并返回
分:在其启动的过程中会做这么几个事情
首先,当我们在启动类执行SpringApplication.run这行代码的时候,在它的方法内部其实会做两个事情
1. 创建SpringApplication对象;
2. 执行run方法。
其次,在创建SpringApplication对象的时候,在它的构造方法内部主要做3个事情。
1. 确认web应用类型,一般情况下是Servlet类型,这种类型的应用,将来会自动启动一个tomcat
2. 从spring.factories配置文件中,加载默认的ApplicationContextInitializer和ApplicationListener
3. 记录当前应用的主启动类,将来做包扫描使用
最后,对象创建好了以后,再调用该对象的run方法,在run方法的内部主要做4个事情
1. 准备Environment对象,它里面会封装一些当前应用运行环境的参数,比如环境变量等等
2. 实例化容器,这里仅仅是创建ApplicationContext对象
3. 容器创建好了以后,会为容器做一些准备工作,比如为容器设置Environment、BeanFactoryPostProcessor后置处理器,并且加载主类对应的Definition
4. 刷新容器,就是我们常说的refresh,在这里会真正的创建Bean实例
总:总结一下我刚刚说的,其实SpringBoot启动的时候核心就两步,
创建SpringApplication对象以及run方法的调用,
在run方法中会真正的实例化容器,并创建容器中需要的Bean实例,最终返回IOC容器初始化
- AbstractApplicationContext.refresh()
-
准备 BeanFactory(DefaultListableBeanFactory)
- 设置 ClassLoader
- 设置 Environment -
- 执行 BeanFactory 后置处理器,在这个阶段解析配置类、扫描并注册 BeanDefinition(只注册,不创建)
-
注册 BeanPostProcessor
-
处理国际化
-
初始化事件多播器 ApplicationEventMulticaster
-
启动 tomcat
-
绑定事件监听器和事件多播器
-
实例化非懒加载的单例 Bean
-
扫尾工作,比如清空实例化时占用的缓存等
-
总结
代码块收起展开
总:IOC容器的初始化,核心工作是在 AbstractApplicationContext.refresh() 方法中完成的
分:在 refresh 方法中主要做了这么几件事情
1. 准备 BeanFactory,在这一块需要给 BeanFactory 设置很多属性,比如类加载器、Environment 等
2. 执行 BeanFactory 后置处理器,这一阶段会扫描要放入到容器中的 Bean 信息,得到对应的 BeanDefinition(注意,这里只扫描,不创建)
3. 注册 BeanPostProcessor,我们自定义的 BeanPostProcessor 就是在这个阶段被加载的,将来 Bean 对象实例化好后需要用到
4. 启动 tomcat
5. 实例化容器中非懒加载的单例 Bean,这里需要说的是,多例 Bean 和懒加载的 Bean 不会在这个阶段实例化,将来用到的时候再创建
6. 当容器初始化完毕后,再做一些扫尾工作,比如清除缓存等
总:简单总结一下,在 IOC 容器初始化的过程中,
首先得准备并执行 BeanFactory 后置处理器,
其次得注册 Bean 后置处理器,并启动 tomcat,
最后需要借助于 BeanFactory 完成 Bean 的实例化 自定义容器属性 & 监听机制
ApplicationContextInitializer
Springboot在不同运行阶段提供了很多拓展点, 满足程序员根据自己的需求运用springboot程序
代码块收起展开
new SpringApplication(...)
↓
准备 Environment
↓
创建 ApplicationContext
↓
执行 ApplicationContextInitializer
↓
refresh 容器
↓
创建 Beanwhy?
1. 有些配置必须在 Bean 创建前就生效
比如你想动态加配置源:
- 远程配置中心
- 启动参数转换
- 某些默认值兜底
因为后面 Bean 一旦开始创建,很多 @Value、@ConfigurationProperties 就已经绑定了。
你这时候再改,黄花菜都凉了。
所以要提前改。
2. 想影响容器的创建过程
比如:
- 提前往容器里注册组件
- 提前给容器放一些共享对象
- 根据环境决定后续加载策略
这类事本质上不是业务逻辑,
而是容器启动策略。
那就得在容器真正 refresh 前做。
3. 给框架或中间件留扩展口
很多框架不可能侵入 Spring 源码。
那它怎么办?
只能利用 Spring 提供的生命周期钩子,在合适的阶段插进去。
ApplicationContextInitializer 就是这种钩子之一。
比如一个中间件想:
- 启动时先探测环境
- 预埋配置
- 注册基础设施 Bean
它总不能等业务 Bean 都建完再弄,那就晚了。
使用方式
实现接口
实现ApplicationContextInitializer接口 并且给context对象注入环境属性,并且将各个属性注册
代码块收起展开
public class MyApplicationContextInitializer implements ApplicationContextInitializer{
@Override //IOC容器创建完毕后执行
public void initialize(ConfigurableApplicationContext applicationContext){
//准备属性
Map<String,Object> myMap = new HashMap<>();
//获取一个属性资源管理对象:先环境再管理
ConfigurableEnvironment envir = applicationContext.getEnvironment();
MutablePropertySources propertySource =
envir.getPropertySources();
//注册
propertySources.addLast(new MapPropertySource("myMap",myMap));
}
}配置文件
在META-INF/spring.factories配置文件中配置定义的类 接口的全类名=实现类的全类名
代码块收起展开
# 接口全路径名称 = 自定义类的全路径名称
org.springframework.context.ApplicationContextInitializer
= com.que.java.MyApplicationContextInitializer总结
ApplicationContextInitializer的使用
- 实现AC接口
- 在META-INF配置文件中配置自定义的类
initialize方法执行时
- IOC容器对象创建完成后执行, 常用于环境属性注册
ApplicationListener
监听容器发布的事件,允许程序员执行自己的代码,完成事件驱动开发, 它可以监听容器初始化完成、初始化失败等事件: 通常情况下可以使用监听器加载资源,开启定时任务等
- IOC容器发布事件之后执行, 通常用于资源加载, 定时任务发布
使用
- 实现ApplicationListener接口
- 在META-INF/spring.factories配置
代码块收起展开
//实现此接口就可以重写onApplicationEvent方法, 判定容器是否初始化成功
public class MyListener implements ApplicationListen{
@Override
public void onApplicationEvent(ApplicationEvent event){
//ApplicationEvent对应的就是发布的时间readyEvent / failEvent
if(event instancof ApplicationReadyEvent){
"myListener初始化成功".sout;
}
if(event instanceof ApplicationFailEvent){
"初始化失败".sout;
}
}
}本文逻辑
很多人在学 SpringBoot 启动流程时,容易把 SpringApplication、IOC 容器、ApplicationContextInitializer、ApplicationListener 这些内容拆开来记。这样看似记了很多知识点,但最后脑子里还是一团乱,因为不知道它们分别在什么时候执行、为什么会出现在这里、彼此之间又是什么关系。
所以本文不按零散知识点展开,而是按一条完整的启动主线来分析:
执行 SpringApplication.run() 之后,SpringBoot 是如何一步步完成环境准备、容器创建、容器初始化、Bean 创建以及事件发布的。
一、本文要解决的核心问题
本文主要回答下面这个问题:
当启动类执行 SpringApplication.run() 之后,SpringBoot 到底做了哪些事,才能把整个应用真正启动起来?
要搞懂这个问题,就不能只盯着某一个类或者某一个接口看,而是必须把整个流程串起来看。
二、先建立整体主线
从大方向来看,SpringBoot 的启动过程可以概括成下面这条链路:
代码块收起展开
创建 SpringApplication 对象
↓
准备运行环境 Environment
↓
创建 IOC 容器 ApplicationContext
↓
执行容器初始化扩展 ApplicationContextInitializer
↓
调用 refresh() 初始化 IOC 容器
↓
完成 Bean 的创建与装配
↓
发布容器相关事件
↓
ApplicationListener 监听事件并执行扩展逻辑这条链路就是本文后面所有内容的总纲。后面每一部分,本质上都只是对这条主线某一个阶段的展开分析。
三、按阶段理解全文内容
为了避免混乱,可以把整个 SpringBoot 启动过程拆成三个阶段来理解。
1. 启动准备阶段
这一阶段还没有真正进入 IOC 容器内部,主要做的是启动前的准备工作,包括:
- 创建
SpringApplication对象 - 判断当前应用类型
- 加载默认的
ApplicationContextInitializer - 加载默认的
ApplicationListener - 推断主启动类
- 准备
Environment
这一阶段的本质不是创建 Bean,而是先把启动程序本身需要的配置和规则准备好。
你可以把它理解成:
SpringBoot 先搭好一个启动框架,告诉自己接下来该用什么环境、什么容器、什么扩展点来完成后续启动。
2. 容器初始化阶段
当启动准备完成后,SpringBoot 会正式创建 ApplicationContext,也就是 IOC 容器,然后进入容器初始化流程。
这一阶段会依次完成这些关键工作:
- 创建 IOC 容器
- 执行
ApplicationContextInitializer - 调用
refresh() - 创建并准备
BeanFactory - 扫描并注册
BeanDefinition - 执行各种后置处理器
- 注册
BeanPostProcessor - 初始化事件广播器等基础设施
- 实例化非懒加载的单例 Bean
这一阶段才是 Spring 启动过程的核心,因为这里真正完成了:
把配置、类信息、注解元数据,转换成容器里可管理、可依赖注入、可被调用的 Bean。
所以严格来说,IOC 容器初始化的重点并不是“有一个容器对象”,而是:
这个容器有没有能力把 Bean 管起来,并把它们真正创建出来。
3. 容器就绪与事件响应阶段
当 refresh() 执行完成后,说明 IOC 容器已经基本准备好了,大部分需要提前创建的 Bean 也已经完成实例化。
这时候 SpringBoot 会继续发布启动过程中的相关事件,比如:
- 容器已准备完成
- 应用已启动成功
- 启动失败
而 ApplicationListener 的作用,就是监听这些事件,并在合适的时机执行我们自定义的逻辑。
比如:
- 做资源加载
- 做启动完成后的通知
- 做**失败日志处理
- 做某些非业务性的初始化动作
所以这一阶段的重点是: 容器已经不是“正在创建中”,而是开始进入“可运行、可响应扩展逻辑”的状态。
四、为什么要先讲启动流程,再讲 IOC 初始化
因为很多人学到后面会有一个特别典型的混乱:
明明在讲 SpringBoot 启动,结果中间突然跳到 refresh();
明明在讲 IOC 容器,结果又突然冒出 Initializer 和 Listener。
根本原因就在于没有先建立“阶段感”。
实际上,这几个知识点并不是彼此独立的,它们本来就在同一条启动链路上,只是所处的位置不同:
SpringApplication负责组织整个启动流程Environment负责提供运行环境参数ApplicationContext是 IOC 容器本体ApplicationContextInitializer作用在容器refresh()之前refresh()负责完成容器初始化Bean的创建发生在refresh()过程中ApplicationListener主要在事件发布后生效
五、ApplicationContextInitializer 和 ApplicationListener 为什么要单独拿出来讲
因为这两个东西很容易和 IOC 初始化主流程混在一起,但它们的定位其实并不一样。
1. ApplicationContextInitializer 的定位
它的执行时机是:
IOC 容器对象创建完成之后,但 refresh() 之前。
也就是说,这时候容器已经有了,但里面的 Bean 还没有真正开始大规模创建。
所以它适合做的事情是:
- 提前修改容器属性
- 提前注册一些环境配置
- 在 Bean 创建前影响容器行为
它本质上属于:
容器初始化前的扩展点。
2. ApplicationListener 的定位
它的执行依赖于事件发布机制。
也就是说,Spring 在启动过程中会发布很多事件,而监听器的作用就是:
监听这些事件,然后执行对应逻辑。
所以它不是直接参与 Bean 创建本身,而是站在流程外部,对启动过程进行“监听和响应”。
它本质上属于:
事件驱动型扩展点。
六、本文各部分之间的衔接关系
为了让后面的内容更清楚,可以先把本文结构理解成下面这组递进关系:
先看 SpringApplication 是如何把启动流程组织起来的,
再看 run() 方法如何推动整个流程向前执行,
接着分析 ApplicationContext 是如何被创建出来的,
然后进入 refresh() 看 IOC 容器是如何真正完成初始化的,
最后再看 Initializer 和 Listener 分别插在流程的哪个位置,以及它们分别解决什么问题。
所以这篇文章虽然分成了几个小节,但本质上一直都在回答同一个问题:
SpringBoot 启动时,应用是如何从“一个 main 方法”逐步变成“一个可运行的 Spring 容器”的。
七、读这篇文章时最应该抓住的主线
看后面的内容时,你只需要始终盯住一件事:
当前这一步,到底是在做启动准备、容器初始化,还是事件响应。
只要这个阶段感不乱,整篇文章就不会乱。
你会发现:
new SpringApplication()属于启动准备run()是总调度入口ApplicationContextInitializer属于容器初始化前扩展refresh()属于 IOC 初始化核心Bean创建属于refresh()的重要结果ApplicationListener属于事件响应机制
SpringBoot 的启动,本质上就是按固定顺序,完成环境准备、容器创建、容器初始化、Bean 装配以及事件发布。
而 ApplicationContextInitializer 和 ApplicationListener,只是这个过程中 SpringBoot 提供给开发者的两个重要扩展点。
所以后文的分析,也都会围绕这条主线展开:
先启动,再建容器;先初始化容器,再创建 Bean;Bean 创建完成后,再通过事件机制扩展后续行为。