imp - SpringBoot启动 & IOC初始化 & 监听机制
SpringBoot 启动流程
new SpringApplication()
- 确认 web 应用的类型
- 加载 ApplicationContextInitializer
- 加载 ApplicationListener
- 记录主启动类
run()
- 准备环境对象 Environment,用于加载系统属性等
- 打印 Banner
- 实例化容器 Context
- 准备容器,为容器设置 Environment、BeanFactoryPostProcessor,并加载主类对应的 BeanDefinition
- 刷新容器(创建 Bean 实例)
- 返回容器
总结
1 | 总:SpringBoot启动,其本质就是加载各种配置信息,然后初始化IOC容器并返回 |
IOC容器初始化
- AbstractApplicationContext.refresh()
准备 BeanFactory(DefaultListableBeanFactory)
- 设置 ClassLoader
- 设置 Environment扫描要放入容器中的 Bean,得到对应的 BeanDefinition (只扫描不创建)
注册 BeanPostProcessor
处理国际化
初始化事件多播器 ApplicationEventMulticaster
启动 tomcat
绑定事件监听器和事件多播器
实例化非懒加载的单例 Bean
扫尾工作,比如清空实例化时占用的缓存等
总结
1 | 总:IOC容器的初始化,核心工作是在 AbstractApplicationContext.refresh() 方法中完成的 |
自定义容器属性 & 监听机制
ApplicationContextInitializer
Springboot在不同运行阶段提供了很多拓展点,
满足程序员根据自己的需求运用springboot程序
1 | new SpringApplication(...) |
why?
1. 有些配置必须在 Bean 创建前就生效
比如你想动态加配置源:
- 远程配置中心
- 启动参数转换
- 某些默认值兜底
因为后面 Bean 一旦开始创建,很多 @Value、@ConfigurationProperties 就已经绑定了。
你这时候再改,黄花菜都凉了。
所以要提前改。
2. 想影响容器的创建过程
比如:
- 提前往容器里注册组件
- 提前给容器放一些共享对象
- 根据环境决定后续加载策略
这类事本质上不是业务逻辑,
而是容器启动策略。
那就得在容器真正 refresh 前做。
3. 给框架或中间件留扩展口
很多框架不可能侵入 Spring 源码。
那它怎么办?
只能利用 Spring 提供的生命周期钩子,在合适的阶段插进去。
ApplicationContextInitializer 就是这种钩子之一。
比如一个中间件想:
- 启动时先探测环境
- 预埋配置
- 注册基础设施 Bean
它总不能等业务 Bean 都建完再弄,那就晚了。
使用方式
实现接口
实现ApplicationContextInitializer接口
并且给context对象注入环境属性,并且将各个属性注册
1 | public class MyApplicationContextInitializer implements ApplicationContextInitializer{ |
配置文件
在META-INF/spring.factories配置文件中配置定义的类
接口的全类名=实现类的全类名
1 | # 接口全路径名称 = 自定义类的全路径名称 |
总结
ApplicationContextInitializer的使用
- 实现AC接口
- 在META-INF配置文件中配置自定义的类
initialize方法执行时
- IOC容器对象创建完成后执行, 常用于环境属性注册
ApplicationListener
监听容器发布的事件,允许程序员执行自己的代码,完成事件驱动开发,
它可以监听容器初始化完成、初始化失败等事件:
通常情况下可以使用监听器加载资源,开启定时任务等
- IOC容器发布事件之后执行, 通常用于资源加载, 定时任务发布
使用
- 实现ApplicationListener接口
- 在META-INF/spring.factories配置
1 |
|
本文逻辑
很多人在学 SpringBoot 启动流程时,容易把 SpringApplication、IOC 容器、ApplicationContextInitializer、ApplicationListener 这些内容拆开来记。这样看似记了很多知识点,但最后脑子里还是一团乱,因为不知道它们分别在什么时候执行、为什么会出现在这里、彼此之间又是什么关系。
所以本文不按零散知识点展开,而是按一条完整的启动主线来分析:
执行 SpringApplication.run() 之后,SpringBoot 是如何一步步完成环境准备、容器创建、容器初始化、Bean 创建以及事件发布的。
一、本文要解决的核心问题
本文主要回答下面这个问题:
当启动类执行 SpringApplication.run() 之后,SpringBoot 到底做了哪些事,才能把整个应用真正启动起来?
要搞懂这个问题,就不能只盯着某一个类或者某一个接口看,而是必须把整个流程串起来看。
二、先建立整体主线
从大方向来看,SpringBoot 的启动过程可以概括成下面这条链路:
1 | 创建 SpringApplication 对象 |
这条链路就是本文后面所有内容的总纲。后面每一部分,本质上都只是对这条主线某一个阶段的展开分析。
三、按阶段理解全文内容
为了避免混乱,可以把整个 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 创建完成后,再通过事件机制扩展后续行为。