imp - SpringBoot启动 & IOC初始化 & 监听机制

SpringBoot 启动流程

new SpringApplication()

  1. 确认 web 应用的类型
  2. 加载 ApplicationContextInitializer
  3. 加载 ApplicationListener
  4. 记录主启动类

run()

  1. 准备环境对象 Environment,用于加载系统属性等
  2. 打印 Banner
  3. 实例化容器 Context
  4. 准备容器,为容器设置 Environment、BeanFactoryPostProcessor,并加载主类对应的 BeanDefinition
  5. 刷新容器(创建 Bean 实例)
  6. 返回容器
Pasted image 20260402212923

总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
总: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()
    1. 准备 BeanFactory(DefaultListableBeanFactory)
      - 设置 ClassLoader
      - 设置 Environment

    2. 扫描要放入容器中的 Bean,得到对应的 BeanDefinition (只扫描不创建)

    3. 注册 BeanPostProcessor

    4. 处理国际化

    5. 初始化事件多播器 ApplicationEventMulticaster

    6. 启动 tomcat

    7. 绑定事件监听器和事件多播器

    8. 实例化非懒加载的单例 Bean

    9. 扫尾工作,比如清空实例化时占用的缓存等

Pasted image 20260402213930

总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
总: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程序

Pasted image 20260402221126
1
2
3
4
5
6
7
8
9
10
11
new SpringApplication(...)

准备 Environment

创建 ApplicationContext

执行 ApplicationContextInitializer

refresh 容器

创建 Bean

why?

1. 有些配置必须在 Bean 创建前就生效

比如你想动态加配置源:

  • 远程配置中心
  • 启动参数转换
  • 某些默认值兜底

因为后面 Bean 一旦开始创建,很多 @Value@ConfigurationProperties 就已经绑定了。
你这时候再改,黄花菜都凉了

所以要提前改。


2. 想影响容器的创建过程

比如:

  • 提前往容器里注册组件
  • 提前给容器放一些共享对象
  • 根据环境决定后续加载策略

这类事本质上不是业务逻辑,
而是容器启动策略

那就得在容器真正 refresh 前做。


3. 给框架或中间件留扩展口

很多框架不可能侵入 Spring 源码。
那它怎么办?

只能利用 Spring 提供的生命周期钩子,在合适的阶段插进去。

ApplicationContextInitializer 就是这种钩子之一。

比如一个中间件想:

  • 启动时先探测环境
  • 预埋配置
  • 注册基础设施 Bean

它总不能等业务 Bean 都建完再弄,那就晚了。

使用方式

实现接口

实现ApplicationContextInitializer接口
并且给context对象注入环境属性,并且将各个属性注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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配置文件中配置定义的类
接口的全类名=实现类的全类名

1
2
3
# 接口全路径名称 = 自定义类的全路径名称
org.springframework.context.ApplicationContextInitializer
= com.que.java.MyApplicationContextInitializer

总结

ApplicationContextInitializer的使用

  • 实现AC接口
  • 在META-INF配置文件中配置自定义的类

initialize方法执行时

  • IOC容器对象创建完成后执行, 常用于环境属性注册

ApplicationListener

监听容器发布的事件,允许程序员执行自己的代码,完成事件驱动开发,
它可以监听容器初始化完成、初始化失败等事件:
通常情况下可以使用监听器加载资源,开启定时任务等

  • IOC容器发布事件之后执行, 通常用于资源加载, 定时任务发布

使用

  • 实现ApplicationListener接口
  • 在META-INF/spring.factories配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

//实现此接口就可以重写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 启动流程时,容易把 SpringApplicationIOC 容器ApplicationContextInitializerApplicationListener 这些内容拆开来记。这样看似记了很多知识点,但最后脑子里还是一团乱,因为不知道它们分别在什么时候执行、为什么会出现在这里、彼此之间又是什么关系

所以本文不按零散知识点展开,而是按一条完整的启动主线来分析:

执行 SpringApplication.run() 之后,SpringBoot 是如何一步步完成环境准备、容器创建、容器初始化、Bean 创建以及事件发布的。


一、本文要解决的核心问题

本文主要回答下面这个问题:

当启动类执行 SpringApplication.run() 之后,SpringBoot 到底做了哪些事,才能把整个应用真正启动起来?

要搞懂这个问题,就不能只盯着某一个类或者某一个接口看,而是必须把整个流程串起来看。


二、先建立整体主线

从大方向来看,SpringBoot 的启动过程可以概括成下面这条链路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
创建 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 容器,结果又突然冒出 InitializerListener

根本原因就在于没有先建立“阶段感”。

实际上,这几个知识点并不是彼此独立的,它们本来就在同一条启动链路上,只是所处的位置不同:

  • 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 容器是如何真正完成初始化的,
最后再看 InitializerListener 分别插在流程的哪个位置,以及它们分别解决什么问题。

所以这篇文章虽然分成了几个小节,但本质上一直都在回答同一个问题:

SpringBoot 启动时,应用是如何从“一个 main 方法”逐步变成“一个可运行的 Spring 容器”的。


七、读这篇文章时最应该抓住的主线

看后面的内容时,你只需要始终盯住一件事:

当前这一步,到底是在做启动准备、容器初始化,还是事件响应。

只要这个阶段感不乱,整篇文章就不会乱。

你会发现:

  • new SpringApplication() 属于启动准备
  • run() 是总调度入口
  • ApplicationContextInitializer 属于容器初始化前扩展
  • refresh() 属于 IOC 初始化核心
  • Bean 创建属于 refresh() 的重要结果
  • ApplicationListener 属于事件响应机制

SpringBoot 的启动,本质上就是按固定顺序,完成环境准备、容器创建、容器初始化、Bean 装配以及事件发布。

ApplicationContextInitializerApplicationListener,只是这个过程中 SpringBoot 提供给开发者的两个重要扩展点。

所以后文的分析,也都会围绕这条主线展开:

先启动,再建容器;先初始化容器,再创建 Bean;Bean 创建完成后,再通过事件机制扩展后续行为。