本文 的 原文 地址
原始的内容,请参考 本文 的 原文 地址
尼恩说在前面
在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团、蚂蚁、得物的面试资格,遇到很多很重要的相关面试题:
阿里一面:如何在SpringBoot启动时执行特定代码?
SpringBoot 的扩展点有哪些?
SpringBoot 的扩展点、和SpringBoot 启动中的发布订阅的事件机制,有什么关系?
SpringBoot 的扩展点 有哪些类型?
你们项目中, 对 SpringBoot 进行过 哪些 扩展?
最近有小伙伴在面 阿里,问到了相关的面试题,可以说是逢面必问。
小伙伴没有系统的去梳理和总结,所以支支吾吾的说了几句,面试官不满意,面试挂了。
所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。
最终,机会爆表,实现”offer自由” 。
当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V175版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请到文末公号【技术自由圈】获取
本文作者:
- 第一作者 老架构师 肖恩(肖恩 是尼恩团队 高级架构师,负责写此文的第一稿,初稿 )
- 第二作者 老架构师 尼恩 (45岁老架构师, 负责 提升此文的 技术高度,让大家有一种 俯视 技术、俯瞰技术、 技术自由 的感觉)
现状:能 暴击面官的 顶级高手,不到10%
经过 尼恩团队 对 社群1000多 5年以上经验的开发小伙伴的了解和分析,真正能在 SpringBoot 的架构和源码这块掌握得好的人很少。
尽管大家都知道 SpringBoot 的架构和源码 非常重要,很多的人,一直在学这块,和研究这块,但是 太多的人 在死记硬背。
甚至说 90%的人, 在死记硬背,过两个月就忘记了。
面试的时候,也回答不到 Spring 的源码 底层思维/ 底层原理,
真正 理解了 Spring 的源码 底层思维/ 底层原理,做到 能 暴击面官的 ,比例 不到10%。
本文,尼恩从设计模式入手 , 带大家 穿透 Spring 的源码 , 帮大家 暴击面官。
问题答案
在 Spring Boot 应用中,主要通过 SpringBoot 的扩展点 ,实现 在启动阶段执行特定代码。
SpringBoot 主要的扩展点
方式 | 执行时机 | 特点 |
---|---|---|
CommandLineRunner | 应用完全启动后,接收原始命令行参数 | 简单直接,适合处理命令行参数 |
ApplicationRunner | 应用完全启动后,接收结构化参数 | 参数处理更灵活 |
@EventListener | 监听特定启动事件 | 可以精确控制执行阶段 |
ApplicationListener | 实现接口监听应用事件 | 更底层的事件控制 |
SmartInitializingSingleton | 所有单例Bean初始化完成后 | 适合需要在所有Bean就绪后执行的逻辑 |
@PostConstruct | Bean初始化完成后 | 仅限于单个Bean的生命周期 |
SpringApplicationRunListener | 贯穿整个启动过程 | 需要实现多个方法,适合框架级扩展 |
BeanPostProcessor | 每个Bean初始化前后 | 粒度最细,可以干预每个Bean的创建过程 |
2、扩展点的执行时机
spring boot 中这么多扩展点,源码中的触发点在那,在那个时机执行的?
如何选择合适的扩展点执行特定代码呢?
Spring Boot扩展点分类 与 选择
如果对于spring boot扩展点做大致分类,可以分为两类
- 事件类 扩展点: 基于 观察者模式 的事件类扩展点 , 被动接收模式
- 非事件类扩展点: 基于 模版模式 的非事件类扩展点(比如后置处理器
BeanFactoryPostProcessor
,CommandLineRunner等) ,主动调用模式
1、扩展点分类统计
根据Spring Boot官方文档及源码分析,我将核心扩展点分类统计如下:
类型 | 扩展点名称/机制 | 数量 | 占比 |
---|---|---|---|
事件类 | ApplicationEvent 子类 | 12+ | 75% |
〰️ | @EventListener 监听机制 | - | |
非事件类 | ApplicationRunner | 1 | 25% |
〰️ | CommandLineRunner | 1 | |
〰️ | SpringApplicationRunListener | 1 | |
〰️ | ApplicationContextInitializer | 1 | |
〰️ | BeanFactoryPostProcessor | 1 | |
总计 | 16+ | 100% |
尼恩社群,一个塔尖、硬核 技术 研究圈 , 我们对 Spring Boot 扩展点分类统计 的研究结果是: 事件类扩展点 占绝对主导(≈75%),非事件类较少(≈25%)
(1) 事件类扩展点 占绝对主导(≈75%):
12个核心事件 + @EventListener
机制
(2) 非事件类扩展点 较少(≈25%):
6个主要接口式扩展点 , 模版模式
(3) 未计入@PostConstruct
等通用扩展机制
2、扩展点选择决策指南
(1)优先选择事件类扩展点的场景(推荐80%场景)
比如: 资源预热 → ApplicationReadyEvent
@EventListener(ApplicationReadyEvent.class)
public void warmupCache() {
// 异步预热缓存
CompletableFuture.runAsync(() -> cacheService.preload());
}
通过尼恩团队的研究, Spring Boot 12个核心启动事件详解
根据 Spring Boot 官方文档(3.x 版本)和源码分析,以下是构成事件类扩展点主体的 12 个核心事件及其触发时机和作用:
序号 | 事件类型 | 触发时机 | 核心作用 | 是否可修改容器 |
---|---|---|---|---|
1 | ApplicationStartingEvent | SpringApplication.run() 执行后立即触发 | 最早介入点,注册自定义监听器 | ❌ |
2 | BootstrapContextInitializedEvent | 引导上下文(BootstrapContext)初始化后 | 配置加密/密钥管理的最佳时机 | ❌ |
3 | ApplicationEnvironmentPreparedEvent | 环境对象(Environment)准备完成,但尚未加载配置文件 | 动态修改配置的最后机会(如:添加 PropertySource) | ✅ |
4 | ApplicationContextInitializedEvent | ApplicationContext 初始化完成,但尚未加载 Bean定义 | 执行早期容器定制(如设置活动 profiles) | ✅ |
5 | ApplicationPreparedEvent | Bean 定义加载完成,但实例化之前 | 最后修改 Bean 定义的机会(添加自定义 BeanPostProcessor) | ✅ |
6 | ContextRefreshedEvent | 上下文完全刷新后触发(Spring Framework 原生事件) | 执行需要完整上下文的操作 | ⚠️ 风险操作 |
7 | WebServerInitializedEvent | 内嵌 Web 服务器(Tomcat/Jetty)启动完成 | 获取服务器端口等运行时信息 | ❌ |
8 | ApplicationStartedEvent | 上下文已刷新,但未触发 ApplicationRunner | 资源预热准备(如缓存加载) | ⚠️ 只读访问 |
9 | AvailabilityChangeEvent (Liveness) | 应用进入活动状态(LivenessState.CORRECT) | Kubernetes 存活探针准备 | ❌ |
10 | ApplicationReadyEvent | 所有 ApplicationRunner 执行完毕 | 业务初始化安全点(可安全访问服务) | ⚠️ 只读访问 |
11 | AvailabilityChangeEvent (Readiness) | 应用就绪可接收流量(ReadinessState.ACCEPTING_TRAFFIC) | Kubernetes 就绪探针触发 | ❌ |
12 | ApplicationFailedEvent | 启动过程任何阶段失败时触发 | 失败处理(如发送告警、记录诊断日志) | ❌ |
注:Spring Boot 2.x 中
ApplicationReadyEvent
和ApplicationStartedEvent
合并为单一事件
(2) 非事件类扩展点的选择 场景(20%特殊情况)
场景 | 推荐扩展点 | 优势 |
---|---|---|
简单命令行参数处理 | CommandLineRunner | 参数直接访问 |
应用级初始化逻辑 | ApplicationRunner | 封装好的 ApplicationArguments |
早期容器操作 | SpringApplicationRunListener | 在事件系统初始化前介入 |
容器刷新前修改Bean定义 | BeanDefinitionRegistryPostProcessor | 深度控制Bean加载 |
为啥 SpringApplicationRunListener
的属于非事件类扩展点?
SpringApplicationRunListener
是 Spring Boot 启动架构中的核心非事件类扩展点,尽管其名称包含 “Listener”,但在扩展机制分类中属于非事件类扩展点。
1、分类依据
1). 实现机制对比
特征 | 事件类扩展点 | SpringApplicationRunListener |
---|---|---|
继承关系 | 实现 ApplicationListener | 独立接口,不继承任何事件接口 |
触发方式 | 被动接收事件广播 | 主动调用(由 SpringApplication 直接触发) |
设计模式 | 观察者模式 | 模板方法模式 |
注册方式 | 通过 Spring Bean 或 SPI | 仅限 SPI(META-INF/spring.factories) |
2). 源码证明
在 Spring Boot 核心源码中:
public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
// 直接调用 RunListener 而非事件机制
listeners.starting(bootstrapContext, this.mainApplicationClass);
listeners.environmentPrepared(...);
// ...
}
}
这明确展示了主动调用的模板方法模式。
2、SpringApplicationRunListener
三重身份
1). 本质:非事件类扩展点
- 实现的是程序化调用契约而非事件监听契约
- 在 Spring 事件系统初始化之前执行(
ApplicationStartingEvent
就是由它发出的)
2). 角色:事件机制的触发器
核心实现类 EventPublishingRunListener
:
public class EventPublishingRunListener implements SpringApplicationRunListener {
@Override
public void starting(...) {
// 它负责发布第一个事件
multicastEvent(new ApplicationStartingEvent(...));
}
}
这是 Spring Boot 事件体系的引擎启动器。
3). 作用:完成 启动生命周期 全流程 的 编排
控制启动阶段流转:
sequenceDiagram
SpringApplication->>RunListener: starting()
RunListener->>EventSystem: 发布ApplicationStartingEvent
SpringApplication->>RunListener: environmentPrepared()
RunListener->>EventSystem: 发布EnvironmentPreparedEvent
3、与事件类扩展点的互动关系
- ▶️ 紫色:
SpringApplicationRunListener
(非事件类) - 🔵 蓝色:事件对象(非扩展点)
- 🟡 黄色:事件监听器(事件类扩展点)
4、为什么容易混淆?
(1) 命名误导:名称含 “Listener” 但非观察者模式
(2) 紧密关联:主要实现类 EventPublishingRunListener
专门处理事件
(3) 时序特殊:在事件系统启用前执行,是事件机制的先决条件
5、正确使用示范
注册为 SPI 扩展点(非事件方式):
// META-INF/spring.factories
org.springframework.boot.SpringApplicationRunListener=\
com.example.CustomRunListener
实现模板方法(非事件接口):
public class CustomRunListener implements SpringApplicationRunListener {
// 非事件机制的回调方法
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
// 直接操作应用上下文
context.getEnvironment().setActiveProfiles("preprod");
}
}
结论
SpringApplicationRunListener
属于非事件类扩展点(模板方法模式),但在架构中承担着:
(1) 事件系统的启动引擎
(2) 生命周期阶段的切换控制器
(3) 底层资源的初始化入口
尽管它与事件机制密不可分,但从扩展点分类原则看,其实现方式、注册机制和执行特点都明确归类于非事件类扩展点(占非事件类扩展的20%)。
正确理解其双重角色对深度定制 Spring Boot 启动流程至关重要。
Spring Boot启动流程原理
所以需要了解spring boot的启动原理,和启动过程中的事件机制,
先来了解Spring boot的启动原理,都有哪些阶段,分别做了什么工作,介入了那些扩展点
Spring Boot 启动就像一个流水线:
Spring Boot 启动就像一个流水线,每个阶段都给你留了插口,你可以在合适的位置插上线,做你想做的事情。
大致的流程如下:
- 启动前打个招呼 → SpringApplicationRunListener
- 环境准备好后加点配置 → ApplicationListener(监听环境事件)
- 上下文准备时改点设置 → ApplicationContextInitializer
- 所有 Bean 准备好后干点事 → ApplicationListener(监听准备完成事件)
- 最后启动完了跑个任务 → ApplicationRunner / CommandLineRunner
这样, 就可以灵活控制 Spring Boot 的启动流程啦!
1、 应用启动前(Application Starting)
扩展点:SpringApplicationRunListener
- 这是 Spring Boot 刚开始启动时的一个监听器。
- 可以在应用还没开始加载配置、创建上下文之前做一些事情。
- 比如记录日志、初始化一些环境信息。
- 需要配合
spring.factories
文件注册使用。
2、 准备环境阶段(Environment Prepared)
扩展点:ApplicationListener(监听 ApplicationEnvironmentPreparedEvent
)
- 此时系统环境已经准备好,但 Spring 容器还没开始创建。
- 可以用来修改环境变量或加载额外的配置文件。
- 比如你想根据某些条件动态添加配置,就可以在这里做。
3、 上下文准备阶段(Context Preparing)
扩展点:ApplicationContextInitializer
- 在 Spring 上下文初始化的时候调用,但还没加载 Bean。
- 可以用来对上下文做一些设置,比如注册属性源、修改配置等。
- 通常通过
spring.factories
或者代码手动添加。
4、 Bean加载前后**(Context Loaded)**
扩展点:ApplicationListener(监听 ApplicationPreparedEvent
)
- 此时所有的配置类已经加载,Bean 定义也已经注册完成。
- 但 Bean 还没有真正被创建出来。
- 可以在这个阶段修改 Bean 的定义。
5、 运行阶段(Application Ready)
扩展点:ApplicationRunner / CommandLineRunner
- 应用完全启动完成后执行。
- 适合做一些启动后的初始化工作,比如预热缓存、加载数据等。
- 区别在于
CommandLineRunner
接收原始命令行参数,而ApplicationRunner
接收封装好的参数。
spring boot启动完整时序图:
阶段一:应用初始化(SpringApplication
构造)
程序入口执行SpringApplication.run
,构建 SpringApplication,该阶段完成Spring Boot启动的应用初始化(SpringApplication构造)
关键点:
(1) 资源加载器(ResourceLoader):用于加载类路径资源,通常传入null,Spring Boot会使用默认实现
(2) 主配置类(primarySources):
-
通常是标注了@SpringBootApplication的启动类
-
后续会作为配置类注册到Spring容器中
(3) Web应用类型推断:
- 通过检查类路径中的关键类来判断应用类型
- 影响后续创建的ApplicationContext类型
(4) 初始化器加载(Spring Boot 的SPI机制):
- 所有初始化器都是从META-INF/spring.factories文件中加载
- 使用SpringFactoriesLoader机制实现SPI(服务发现)
(5) 主类推断:
- 通过分析异常堆栈来定位真正的启动类
- 确保后续的组件扫描等操作基于正确的基准包
设计意图:
(1) 约定优于配置:
- 自动推断Web类型和主类,减少显式配置
(2) 扩展性:
-
通过spring.factories机制支持三方扩展
-
初始化器和监听器提供了多个扩展点
(3) 模块化设计:
- 将不同职责分离到专门的组件中
- 如BootstrapRegistryInitializer用于早期初始化
源码如下
/**
* SpringApplication 构造函数,用于初始化应用启动的核心配置
*
* @param resourceLoader 资源加载器,用于加载类路径资源(通常为null)
* @param primarySources 主配置类,通常是包含@SpringBootApplication注解的启动类
*/
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 设置资源加载器
this.resourceLoader = resourceLoader;
// 校验主配置类不能为空
Assert.notNull(primarySources, "PrimarySources must not be null");
// 将主配置类转换为Set集合存储
// 使用LinkedHashSet保证顺序且避免重复
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推断Web应用类型(Servlet/Reactive/None)
// 通过检查类路径中是否存在特定类来判断:
// 1. 如果存在DispatcherHandler且不存在DispatcherServlet -> REACTIVE
// 2. 如果存在Servlet相关类 -> SERVLET
// 3. 否则 -> NONE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 加载并实例化BootstrapRegistryInitializer
// 这些初始化器会在应用启动的早期阶段执行
// 用于引导注册表的初始化工作
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 加载并设置ApplicationContextInitializer
// 这些初始化器会在ApplicationContext创建后、刷新前执行
// 用于对上下文进行自定义初始化
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 加载并设置ApplicationListener
// 这些监听器会监听Spring应用的各种事件
// 如环境准备事件、上下文刷新事件等
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 推断主启动类(包含main方法的类)
// 通过分析调用栈找到真正的启动类
this.mainApplicationClass = deduceMainApplicationClass();
}
阶段二:环境准备(prepareEnvironment
)
接下来执行SpringApplication.run开始部分代码,主要调用prepareEnvironment,进行环境准备
关键点:
(1) BootstrapContext:
-
生命周期:从启动开始到环境准备完成
-
典型用途:早期注册单例Bean(如日志系统)
(2) SpringApplicationRunListeners:重要事件顺序:
- starting() - 应用启动
- environmentPrepared() - 环境就绪
- contextPrepared() - 上下文准备
- contextLoaded() - 上下文加载
- started() - 应用已启动
- running() - 应用运行中
设计模式:
(1) 观察者模式:
-
通过事件监听器实现各阶段解耦
-
例如ConfigFileApplicationListener处理配置文件加载
(2) 模板模式:
- 模版方法(本方法)
- 钩子方法:子类可通过重写特定步骤(如prepareEnvironment)定制行为
(3) 工厂方法模式:
- getRunListeners()隐藏具体监听器创建细节
源码如下:
/**
* Spring Boot应用启动的核心执行方法
*
* @param args 命令行参数
* @return 已初始化的应用上下文
*/
public ConfigurableApplicationContext run(String... args) {
// 1. 记录启动开始时间(纳秒级精度,用于计算总启动耗时)
long startTime = System.nanoTime();
// 2. 创建引导上下文(BootstrapContext)
// 用于在应用上下文创建前的早期初始化阶段共享对象
// 主要存储需要在环境准备阶段就使用的Bean
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 3. 声明应用上下文变量(稍后初始化)
ConfigurableApplicationContext context = null;
// 4. 配置Headless模式(无图形界面模式)
// 设置系统属性java.awt.headless=true
// 确保在服务器环境下不尝试启动图形界面
configureHeadlessProperty();
// 5. 获取运行监听器实例
// 通过SpringFactoriesLoader从META-INF/spring.factories加载
// 监听器将接收整个启动过程的生命周期事件
SpringApplicationRunListeners listeners = getRunListeners(args);
// 6. 发布应用启动事件(ApplicationStartingEvent)
// 此时环境尚未准备,上下文也未创建
// 适合执行最早的初始化操作
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 7. 封装命令行参数
// 将原始String[] args转换为ApplicationArguments对象
// 提供方便的参数访问方法(如--开头的可选参数)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 8. 准备环境配置(核心步骤)
// a. 创建环境对象(根据Web应用类型)
// b. 配置PropertySources(命令行 > 环境变量 > 配置文件)
// c. 处理激活的Profiles
// d. 发布ApplicationEnvironmentPreparedEvent事件
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// ...(后续步骤:上下文创建、刷新、后置处理等)
}
// ...(异常处理逻辑)
}
接下来看下prepareEnvironment
方法
/**
* 准备应用运行环境配置
*
* @param listeners 运行监听器集合,用于发布环境准备事件
* @param bootstrapContext 引导上下文,用于早期初始化
* @param applicationArguments 应用启动参数
* @return 配置完成的环境对象
*/
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext,
ApplicationArguments applicationArguments) {
// 1. 创建或获取环境实例(根据webApplicationType创建对应环境)
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 2. 配置环境对象
// - 添加命令行参数到环境属性源
// - 配置默认的profiles等
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 3. 将ConfigurationProperty源附加到环境
// 使得@ConfigurationProperties注解的属性可以绑定
ConfigurationPropertySources.attach(environment);
// 4. 发布环境准备就绪事件(ApplicationEnvironmentPreparedEvent)
// 允许监听器修改环境配置(如添加自定义属性源)
listeners.environmentPrepared(bootstrapContext, environment);
// 5. 将默认属性源移动到属性源链末尾
// 确保应用配置优先于默认值
DefaultPropertiesPropertySource.moveToEnd(environment);
// 6. 校验环境配置合法性
// 禁止通过属性设置spring.main.environment-prefix
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
// 7. 将环境属性绑定到SpringApplication配置
// 将环境中的spring.main.*属性应用到当前SpringApplication实例
bindToSpringApplication(environment);
// 8. 如果环境不是自定义类型,转换为标准环境对象
// 确保环境类型与webApplicationType匹配
if (!this.isCustomEnvironment) {
environment = convertEnvironment(environment);
}
// 9. 再次附加ConfigurationProperty源(确保修改后的环境正确配置)
ConfigurationPropertySources.attach(environment);
return environment;
}
关键点:
(1) 环境创建:根据webApplicationType
创建对应环境(Servlet/Reactive/Default)
(2) 配置优先级:
-
通过
ConfigurationPropertySources.attach()
建立属性绑定机制 -
DefaultPropertiesPropertySource.moveToEnd()
确保应用配置优先
(3) 事件通知:
environmentPrepared
事件允许外部修改环境配置- 典型监听器:
ConfigFileApplicationListener
加载配置文件
(4) 环境绑定:bindToSpringApplication()
将环境中的spring.main.*
属性绑定到当前应用
(5) 环境转换:确保最终环境类型与应用类型匹配(如将StandardEnvironment
转为StandardServletEnvironment
)
属性源加载顺序:
(1) 命令行参数(最高优先级)
(2) JNDI属性
(3) Java系统属性
(4) 操作系统环境变量
(5) 随机属性(random.*)
(6) 应用配置文件
(7) 默认属性(最低优先级)
阶段三:容器创建与准备(createApplicationContext
&prepareContext
)
继续执行SpringApplication#run()
方法
关键步骤:
(1) configureIgnoreBeanInfo():
-
设置
spring.beaninfo.ignore
系统属性(默认true) -
禁用JavaBean Introspection机制,提升启动速度
(2) printBanner():
-
指定位置的banner文件(通过
spring.banner.location
配置) -
classpath下的banner.txt/banner.jpg等
-
默认Spring Boot Banner
-
可通过实现
Banner
接口完全自定义
(3) createApplicationContext():创建容器
-
通过反射实例化上下文类
-
上下文类型与web应用类型严格对应:
switch (webApplicationType) { case SERVLET: return new AnnotationConfigServletWebServerApplicationContext(); case REACTIVE: return new AnnotationConfigReactiveWebServerApplicationContext(); default: return new AnnotationConfigApplicationContext(); }
(4) prepareContext():准备容器
-
注册主配置类(
primarySources
) -
添加BeanName生成器
-
设置资源加载器
-
执行
ApplicationContextInitializer
-
发布
ContextPrepared
/ContextLoaded
事件
设计亮点:
(1) 上下文类型自适应:根据前期推断的webApplicationType
自动选择正确的上下文实现
(2) 启动监控:ApplicationStartup
接口记录各阶段耗时(需配合BufferingApplicationStartup
使用)
(3) 可扩展性:
-
通过
Banner
接口支持完全自定义启动图案 -
通过
ApplicationContextInitializer
支持上下文预配置
(4) 性能优化:
- 禁用不必要的JavaBean Introspection机制
- 延迟初始化策略(通过
spring.main.lazy-initialization
配置)
public ConfigurableApplicationContext run(String... args) {
// ... 前序代码省略
try {
// ... 环境准备代码省略
// 1. 配置需要忽略的Bean信息(通过spring.beaninfo.ignore系统属性)
// 通常设置为true以优化启动性能(跳过JavaBean Introspection)
configureIgnoreBeanInfo(environment);
// 2. 打印Banner(启动图案)
// 根据环境配置决定是否打印,支持自定义banner.txt/banner.jpg等
Banner printedBanner = printBanner(environment);
// 3. 创建应用上下文实例
// 根据webApplicationType创建对应类型的上下文:
// - SERVLET: AnnotationConfigServletWebServerApplicationContext
// - REACTIVE: AnnotationConfigReactiveWebServerApplicationContext
// - NONE: AnnotationConfigApplicationContext
context = createApplicationContext();
// 4. 设置应用启动指标收集器
// 用于记录启动过程中各个阶段的耗时(通过ApplicationStartup接口实现)
context.setApplicationStartup(this.applicationStartup);
// 5. 准备应用上下文
// - 注册主配置类(primarySources)
// - 初始化Bean定义读取器和扫描器
// - 发布ContextPrepared/ContextLoaded事件
prepareContext(bootstrapContext, context, environment, listeners,
applicationArguments, printedBanner);
// ... 后续刷新上下文等代码省略
}
// ... 异常处理代码省略
}
阶段四:容器刷新(refreshContext
)
/**
* Spring Boot应用启动的核心方法,负责创建和刷新应用上下文
*
* @param args 命令行参数(通常来自Java main方法)
* @return 已初始化并刷新完成的应用上下文
*/
public ConfigurableApplicationContext run(String... args) {
// ...(前序代码:环境准备、上下文创建等)
// 核心阶段:刷新应用上下文
// 此方法会完成IoC容器的初始化、Bean的实例化和依赖注入等关键操作
refreshContext(context);
// ...(后续代码:启动后处理、事件发布等)
}
调用refreshContext 方法:
- 完成Spring容器的初始化工作
- 触发自动配置机制(@EnableAutoConfiguration)
- 对于Web应用会启动内嵌服务器,tomcat就是在12大步骤中的onRefresh()中启动
private void refreshContext(ConfigurableApplicationContext context) {
// 实际调用AbstractApplicationContext.refresh()
refresh(context);
// 注册关闭钩子(确保应用关闭时优雅释放资源)
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
} catch (AccessControlException ex) {
// 安全环境下忽略
}
}
}
AbstractApplicationContext#refresh这个方法完成了Spring Boot最核心的容器初始化工作,是自动配置、依赖注入等功能的基础执行阶段。
12大步骤非常重要,参考上一篇文章spring 容器本质
这里有12大关键步骤如下:
**(1) prepareRefresh() - 准备刷新: **
初始化启动标志、属性源校验、早期事件集合
**(2) obtainFreshBeanFactory() - 获取新BeanFactory **
创建/刷新BeanFactory,加载Bean定义,XML/注解配置解析、Bean定义注册发生在这里
(3) prepareBeanFactory() - 配置标准BeanFactory:‘
设置类加载器、注册标准PostProcessor(比如:ApplicationContextAwareProcessor)
(4) postProcessBeanFactory() - 后处理BeanFactory:
子类扩展点,修改Bean定义,比如Web容器注册Servlet相关处理器
(5) invokeBeanFactoryPostProcessors() - 执行工厂后处理器:
@Configuration解析、@Value(${url.jdbc})占位符注入
(6) registerBeanPostProcessors() - 注册Bean后处理器:
AOP代理创建、@Autowired注入
(7) initMessageSource() - 初始化国际化
(8) initApplicationEventMulticaster() - 初始化事件广播器
(9) onRefresh() - 子类扩展刷新:
模板方法供子类扩展,Spring Boot启动内嵌服务器在这
(10) registerListeners() - 注册监听器
(11) finishBeanFactoryInitialization() - 完成单例初始化:
单例bean从这里开始创建
(12) finishRefresh() - 完成刷新
12大关键步骤总结归类一下:
(1) 准备阶段:
-
初始化启动时间戳
-
验证必需属性
-
初始化事件监听器集合
(2) BeanFactory创建:
// 创建并配置BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
- 加载Bean定义(包括@Configuration类)
- 注册环境相关的Bean
(3) 后置处理器阶段:
- 执行所有BeanFactoryPostProcessor
- 注册BeanPostProcessor
(4) 初始化阶段:
- 初始化MessageSource(国际化)
- 初始化事件广播器
- 执行onRefresh()(tomcat在此启动服务器)
(5) 完成阶段:
- 完成单例Bean的实例化
- 发布ContextRefreshedEvent事件
设计要点:
- 采用模板方法模式定义标准流程
- 通过事件机制实现各阶段解耦
- 支持条件化装配(@Conditional)
- 确保线程安全的初始化过程
阶段五:后置处理(afterRefresh
)
/**
* Spring Boot应用启动的收尾阶段,完成启动后处理并返回就绪的应用上下文
*
* @param args 命令行参数(通常来自Java main方法)
* @return 完全初始化的应用上下文
*/
public ConfigurableApplicationContext run(String... args) {
// ...(前序代码:上下文创建和刷新)
try {
// 1. 执行刷新后处理(模板方法,默认空实现)
// 子类可在此添加自定义逻辑
afterRefresh(context, applicationArguments);
// 2. 计算总启动耗时(纳秒级精度)
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
// 3. 记录启动信息(如果启用日志)
if (this.logStartupInfo) {
// 输出标准启动日志,包含:
// - 应用名称
// JVM版本
// 激活的profile
// 启动耗时
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), timeTakenToStartup);
}
// 4. 发布ApplicationStartedEvent事件
// 此时上下文已刷新,但CommandLineRunner尚未执行
listeners.started(context, timeTakenToStartup);
// 5. 执行所有Runner实现类
// 包括ApplicationRunner和CommandLineRunner
// 按@Order顺序执行
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 6. 异常处理:停止上下文并发布失败事件
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
// 7. 计算完全就绪耗时
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
// 8. 发布ApplicationReadyEvent事件
// 标志应用完全就绪(所有Runner执行完毕)
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
// 9. 就绪阶段异常处理
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
// 10. 返回完全初始化的应用上下文
return context;
}
关键执行流程解析:
(1) Runner执行机制:在上下文刷新后,应用完全就绪前,根据优先级顺序执行:
@Order(Ordered.HIGHEST_PRECEDENCE)
public class HighPriorityRunner implements ApplicationRunner {
// 最先执行
}
@Order(Ordered.LOWEST_PRECEDENCE)
public class LowPriorityRunner implements CommandLineRunner {
// 最后执行
}
(2) 耗时统计维度:
统计点 | 测量阶段 | 典型日志输出 |
---|---|---|
timeTakenToStartup | 从启动到上下文刷新完成 | “Started MyApp in 2.305 seconds” |
timeTakenToReady | 从启动到所有Runner执行完毕 | 通过Actuator的/startup端点获取 |
(3) 异常处理策略:
private void handleRunFailure(ConfigurableApplicationContext context,
Throwable exception,
SpringApplicationRunListeners listeners) {
// 1. 尝试关闭上下文
try {
if (context != null && context.isActive()) {
context.close();
}
}
// 2. 发布失败事件
finally {
if (listeners != null) {
listeners.failed(context, exception);
}
}
}
设计模式:
(1) 模板方法模式:
-
afterRefresh()
为子类提供扩展点 -
默认空实现(钩子方法)
(2) 观察者模式:
- 通过
SpringApplicationRunListeners
发布生命周期事件 - 支持
ApplicationListener
实现类监听事件
(3) 策略模式:
ApplicationRunner
和CommandLineRunner
提供不同的参数处理策略- 统一通过
callRunners()
方法调度
典型应用场景:
(1) 启动时数据初始化:
@Component
@Order(1)
public class DatabaseInitializer implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
// 初始化数据库数据
}
}
(2) 健康检查预热:
@Component
public class CacheWarmer implements CommandLineRunner {
@Override
public void run(String... args) {
// 预热缓存
}
}
(3) 启动参数处理:
@Bean
public ApplicationRunner customArgParser() {
return args -> {
if (args.containsOption("debug")) {
// 启用调试模式
}
};
}
Spring Boot 启动中的事件机制
1、事件机制原理
1)核心类图
Spring Boot 的事件机制是基于 Spring 框架里的 ApplicationEvent
来实现的,它用的是“发布-订阅”这种模式,简单说就是有人发消息,有人接收处理。主要靠一个叫 ApplicationEventMulticaster
的工具来广播事件。
Spring Boot 的事件系统建立在 Spring Framework 的 ApplicationEvent
机制上,通过 ApplicationEventMulticaster
实现发布-订阅模式。
以下是核心类图:
这些类的作用可以这么理解:
- ApplicationEvent 是所有事件的“老祖宗”,每个事件都带有一个触发来源(source)和发生时间戳。
- SpringApplicationEvent 是 Spring Boot 特有的事件基类,里面加了个启动参数数组 args,就像你运行程序时传进去的命令行参数。
- 各种具体的事件,比如:
ApplicationStartingEvent
:程序刚启动时触发;ApplicationEnvironmentPreparedEvent
:环境配置准备好了;ApplicationContextInitializedEvent
:上下文初始化完成;ApplicationPreparedEvent
:应用基本准备就绪;ApplicationStartedEvent
:应用开始运行;ApplicationReadyEvent
:应用完全启动完毕,可以接收请求了。
- ApplicationListener 是监听器接口,谁想监听某个事件,就实现这个接口,然后写好处理逻辑。
- EventPublishingRunListener 是事件广播的发起者,它内部用了
SimpleApplicationEventMulticaster
来把事件发出去。
整个流程就像是在不同启动阶段放了“通知点”,有需要的人可以监听这些通知,做一些额外操作,比如打日志、初始化资源等。
2)事件发布
在 SpringApplication.run()
方法中,事件通过 SpringApplicationRunListeners
发布:
// org.springframework.boot.SpringApplication
public ConfigurableApplicationContext run(String... args) {
// ...
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass); // 发布ApplicationStartingEvent
// ...
}
EventPublishingRunListener
实现了SpringApplicationRunListener 是实际的事件转发器
这些事件具体是怎么发出去的呢?
靠的是一个叫 EventPublishingRunListener
的类。它实现了 SpringApplicationRunListener
接口,是真正负责把事件广播出去的那个“通讯员”。
// org.springframework.boot.context.event.EventPublishingRunListener
public class EventPublishingRunListener implements SpringApplicationRunListener {
private final SimpleApplicationEventMulticaster initialMulticaster;
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(
new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application,
this.args, environment));
}
// 其他事件方法...
}
你可以这么理解:
- 程序一启动,就会有人(listeners)开始发通知(事件)
EventPublishingRunListener
就像个广播站,收到消息后就用multicastEvent
把事件发给所有想听的人(监听器)- 比如“环境准备好了”、“应用开始启动了”这种消息,就是通过这个机制发出去的
3) 事件监听
我们可以简单理解为,Spring Boot 在启动的时候,会自动去找一些“监听器”,用来监听程序启动过程中的各种事件。
这些监听器是从一个叫 META-INF/spring.factories
的配置文件里读取出来的。
Spring Boot 有个工具类叫 SpringFactoriesLoader
,它的作用就像是个“名单读取器”,根据名字去找到这些监听器。
监听器通过 SpringFactoriesLoader
从 META-INF/spring.factories
加载:
// org.springframework.boot.SpringApplication
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = { SpringApplication.class, String[].class };
List<SpringApplicationRunListener> listeners = new ArrayList<>(
SpringFactoriesLoader.loadFactories(SpringApplicationRunListener.class, types, getClassLoader()));
// ...
}
它就是在干这么一件事:
(1) 定义了两个参数类型(就是启动类和命令行参数)
(2) 然后用 SpringFactoriesLoader.loadFactories()
方法,去配置文件里找所有实现了 SpringApplicationRunListener
接口的类,把它们一个个创建出来,作为监听器使用。
你可以把 SpringFactoriesLoader
想象成一个“插件管理员”,它会按照 spring.factories
这个名单,把需要的插件一个个请过来干活。
所以,EventPublishingRunListener
有一个默认的实现,除了默认的, 可以有自定义的实现。但在Spring Boot框架内部,只有这一个默认实现。
Spring Boot中SpringApplicationRunListener的唯一默认实现类是:
EventPublishingRunListener
这个实现类位于spring-boot
项目的org.springframework.boot.context.event
包中。
但是,请注意,用户可以通过在META-INF/spring.factories
文件中配置来添加自定义的SpringApplicationRunListener
实现。
例如:
org.springframework.boot.SpringApplicationRunListener=com.example.CustomSpringApplicationRunListener
2、启动中的事件
就像做饭一样,Spring Boot 启动过程就像一步步准备食材、开火、炒菜。
每个事件就像是一个关键步骤,比如洗菜、切菜、炒熟、装盘,每一步都可以插手去做点事。
事件类型 | 触发时机 | 源码触发点 | 典型用途 |
---|---|---|---|
ApplicationStartingEvent | 启动最初阶段 | SpringApplicationRunListeners.starting() | 初始化日志系统、注册监控组件、记录启动时间 |
ApplicationEnvironmentPreparedEvent | 环境准备完成 | prepareEnvironment() | 动态修改环境变量、添加加密属性源、加载外部配置 |
ApplicationContextInitializedEvent | 上下文初始化完成 | prepareContext() | 早期Bean注册、自定义条件化配置、修改Bean定义 |
ApplicationPreparedEvent | Bean定义加载完成 | refreshContext() 前 | 动态注册Bean定义、修改Bean属性值、准备AOP代理 |
ContextRefreshedEvent | 上下文刷新完成 | AbstractApplicationContext.refresh() | 缓存预热、初始化静态数据、执行一次性计算任务 |
WebServerInitializedEvent | Web服务器就绪 | WebServerManager.initialize() | 获取实际绑定端口、注册服务发现、初始化HTTP连接池 |
ApplicationStartedEvent | 上下文刷新后 | afterRefresh() 后 | 服务注册、分布式锁获取、集群选主 |
AvailabilityChangeEvent(Liveness) | 应用存活状态 | EventPublishingRunListener.started() | Kubernetes存活探针、服务健康状态上报 |
ApplicationReadyEvent | 所有Runner完成 | callRunners() 后 | 流量接入、定时任务启动、发送就绪通知 |
AvailabilityChangeEvent(Readiness) | 准备接收流量 | EventPublishingRunListener.ready() | Kubernetes就绪探针、负载均衡注册、灰度发布准备 |
ApplicationFailedEvent | 启动过程异常 | handleRunFailure() | 资源清理、异常上报、告警触发 |
3、关键事件解析
1) ApplicationEnvironmentPreparedEvent
Spring 在启动过程中,会先准备好运行环境。
这部分逻辑在 prepareEnvironment
方法中完成。
简单说就是:
- 先创建一个配置环境(可以理解成程序运行需要的各种设置);
- 然后通知所有监听器:“环境准备好了”;
- 接着把配置信息绑定到 Spring 应用上;
- 最后把这个环境返回,供后续使用。
源码触发路径:
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext) {
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 环境准备后立即发布事件
listeners.environmentPrepared(bootstrapContext, environment);
bindToSpringApplication(environment);
return environment;
}
使用案例:动态修改配置
有时候我们不想把敏感信息(比如数据库密码)明文写在配置文件里,而是加密之后再放进去,比如写成这样:ENC(xxx)
。等程序启动的时候,再自动把它解开。
这个例子中的 EncryptionConfigListener
就是干这事的。
它的原理很简单:
- 当 Spring 准备好配置后,会发个消息(事件),这个监听器就能收到;
- 它遍历所有的配置项,看看有没有值是以
ENC(
开头的; - 如果有,就调用解密方法,把真实值还原出来;
- 然后把这些解密后的值加回配置里,让后面的程序能正常读取。
你可以把它想象成一个“自动开锁工具”,Spring 启动时自动帮你把加密的配置打开,不影响正常使用。
这里创建了一个 ApplicationListener 实现类,监听 ApplicationEnvironmentPreparedEvent 事件:
public class EncryptionConfigListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment env = event.getEnvironment();
for (PropertySource<?> ps : env.getPropertySources()) {
if (ps instanceof EnumerablePropertySource) {
Map<String, Object> decrypted = new HashMap<>();
for (String key : ((EnumerablePropertySource<?>) ps).getPropertyNames()) {
Object value = ps.getProperty(key);
if (value instanceof String strValue && strValue.startsWith("ENC(")) {
decrypted.put(key, decrypt(strValue));
}
}
env.getPropertySources().addAfter(ps.getName(),
new MapPropertySource("decrypted-" + ps.getName(), decrypted));
}
}
}
}
2) ApplicationPreparedEvent
源码关键点:
private void refreshContext(ConfigurableApplicationContext context) {
// Bean定义加载完成后触发
listeners.contextLoaded(context);
// 实际发布在AbstractApplicationContext.refresh()
}
简单说,就是在所有配置文件读完、Bean 定义加载完之后,会通知一些监听器(就像通知小助手),告诉它们:“东西都准备好了,你们可以开始干活了。”
使用案例:动态注册Bean
有时候我们希望在程序启动的时候,根据不同的情况,动态地加一个 Bean 进去。比如下面这个例子:
public class DynamicBeanRegistrar implements ApplicationListener<ApplicationPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(FeatureToggleService.class);
definition.getPropertyValues().add("features", getRuntimeFeatures());
((GenericApplicationContext) event.getApplicationContext())
.getBeanFactory().registerBeanDefinition("featureToggle", definition);
}
}
这段代码的意思是:当 Spring Boot 应用准备得差不多了,就动态地注册一个叫 featureToggle
的 Bean。
你可以把它想象成:你家装修快结束了,这时候决定要不要装个智能灯。如果条件符合,就把这个功能“动态”加上去。
GenericBeanDefinition
就像是一个说明书模板。setBeanClass
是指定你要注册哪个类。getPropertyValues().add(...)
是给这个类设置参数。- 最后调用
registerBeanDefinition
把这个 Bean 注册到 Spring 容器里。
整个过程就像是在装修快完成时,临时加装一个设备,不影响整体结构,但能增加新功能。
3) ApplicationReadyEvent 与 StartedEvent 区别
源码对比:
// org.springframework.boot.SpringApplication
private void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
// 1. 先发布StartedEvent
listeners.started(context, timeTakenToStartup);
// 2. 执行Runners
callRunners(context, args);
// 3. 再发布ReadyEvent
listeners.ready(context, timeTakenToReady);
}
Spring Boot 启动时,在准备好应用上下文后,会先触发一个叫 StartedEvent
的事件,接着执行一些启动任务(Runners),最后再触发一个 ReadyEvent
事件。
你可以理解成:就像早上起床做准备一样,先穿好衣服(StartedEvent),然后吃早饭(Runners),最后出门上班(ReadyEvent)。
使用场景选择:
StartedEvent
:适用于需要抢在Runners前执行的初始化ReadyEvent
:适用于所有服务就绪后的操作
@Component
public class ClusterCoordinator {
@EventListener
public void onStarted(ApplicationStartedEvent event) {
// 抢先注册节点
registerNode();
}
@EventListener
public void onReady(ApplicationReadyEvent event) {
// 等待所有服务就绪后开始选举
startLeaderElection();
}
}
4) AvailabilityChangeEvent 机制
状态转换逻辑:
// org.springframework.boot.availability.AvailabilityChangeEvent
public static void publish(ApplicationContext context, AvailabilityState state) {
if (context != null && context.isActive()) {
context.publishEvent(new AvailabilityChangeEvent<>(context, state));
}
}
// 在EventPublishingRunListener中
public void started(ConfigurableApplicationContext context, Duration timeTaken) {
context.publishEvent(new ApplicationStartedEvent(...));
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT); // 存活状态
}
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
context.publishEvent(new ApplicationReadyEvent(...));
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC); // 就绪状态
}
Spring Boot 在启动过程中,会根据应用的状态变化,对外发布相应的事件。
这里有两个关键的状态通知:
(1) started 方法:
当应用刚启动完成时,会触发一个叫“ApplicationStartedEvent”的事件,表示程序开始跑了。紧接着还会发一个“存活状态(LivenessState.CORRECT)”的通知,告诉系统它已经正常运行了。
(2) ready 方法:
当应用准备好、可以接收外部请求时,又会触发“ApplicationReadyEvent”事件,并发出“就绪状态(ReadinessState.ACCEPTING_TRAFFIC)”的消息,说明它可以对外提供服务了。
你可以把这两个状态理解成:
- 存活状态就像人醒了,能动了;
- 就绪状态就像人不仅醒了,还穿好衣服、泡好茶,准备上班干活了。
代码部分没做改动,只是做了说明上的解释。
4、事件扩展点案例
1) 配置加密解密(EnvironmentPreparedEvent)
这是一个 Java 的监听器类,用来在程序启动的时候,把加密的配置信息自动解密。
代码解释如下:
- 这个类叫
DecryptEnvironmentListener
,它实现了一个叫ApplicationListener
的接口,监听的是程序启动过程中“环境准备好之前”这个事件(也就是ApplicationEnvironmentPreparedEvent
)。 - 在事件触发时,会执行
onApplicationEvent
方法。 - 这个方法的作用是拿到当前程序的配置环境,然后把里面一个叫
"encrypted"
的配置源替换成一个能自动解密的配置源。 - 说白了就是:程序启动时,发现有些配置是加密的,这个类负责让程序能看懂这些加密内容。
你可以把它想象成一把钥匙,在程序启动的时候,用这把钥匙把“看不懂的加密内容”打开,变成“看得懂的明文配置”。
public class DecryptEnvironmentListener implements
ApplicationListener<ApplicationEnvironmentPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment env = event.getEnvironment();
env.getPropertySources().replace("encrypted",
new DecryptingPropertySource("encrypted",
env.getPropertySources().get("encrypted")));
}
}
2) 动态数据源注册(ApplicationPreparedEvent)
public class DynamicDataSourceRegistrar implements
ApplicationListener<ApplicationPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
GenericApplicationContext ctx = (GenericApplicationContext) event.getApplicationContext();
ctx.getBeanFactory().registerBeanDefinition("dynamicDataSource",
BeanDefinitionBuilder.rootBeanDefinition(HikariDataSource.class)
.addPropertyValue("jdbcUrl", "${custom.datasource.url}")
.getBeanDefinition());
}
}
这是一个叫 DynamicDataSourceRegistrar
的类,它做的事情是在 Spring 程序启动的时候,提前注册一个叫 dynamicDataSource
的数据源。
你可以把“数据源”理解成是程序连接数据库的一条通道。
这个类在程序刚准备好的时候(还没完全启动完),就会去创建这个通道,并且把它放进 Spring 容器里,让整个程序能用上。
它用的是 Hikari 这种高效的连接池来创建数据源,配置里的数据库地址是从配置文件中读取的,写法是 ${custom.datasource.url}
,意思是具体地址可以在配置文件里指定。
代码本身就是在告诉 Spring:用 Hikari 来创建一个叫 dynamicDataSource
的数据源,然后注册到容器里。
3 )流量预热(ApplicationReadyEvent)
这段代码的作用是,在系统正式启动后,自动发起 100 次并发请求访问 /warmup
接口,目的是让系统提前“热身”,运行更稳定。而且它只在正式上线环境中生效。
@Profile("prod")
@Component
public class TrafficWarmup {
@EventListener
public void onReady(ApplicationReadyEvent event) {
RestTemplate rest = event.getApplicationContext().getBean(RestTemplate.class);
IntStream.range(0, 100).parallel().forEach(i -> {
rest.getForObject("/warmup", String.class);
});
}
}
(1) 标记这是生产环境配置
代码开头有个 @Profile("prod")
,意思是这个功能只在正式上线环境起作用,开发或测试阶段不会运行它。
你可以把它想象成一个开关标签:只有在“正式模式”下才会启动这个功能。
(3) 定义一个叫 TrafficWarmup 的类
这个类的名字是 TrafficWarmup
,意思是“流量预热”。
它的主要任务是在系统刚启动时,提前模拟一些访问请求,让系统“热起来”,避免冷启动时响应慢。
这就好比你开车前先热车,让发动机运转顺畅,再上路。
(4) 监听系统启动完成事件
@EventListener
表示这是一个监听器方法,它会监听系统的某个事件。
这里监听的是 ApplicationReadyEvent
—— 也就是整个应用已经完全启动好了。
就像听到一声“准备就绪”的提示音,然后开始执行接下来的动作。
(5) 获取 RestTemplate 工具
代码里通过 event.getApplicationContext().getBean(RestTemplate.class)
获取了一个叫 RestTemplate
的工具。这个工具可以用来发送 HTTP 请求,比如访问网页接口。
你可以把它理解为一个“自动浏览器”,能帮我们偷偷地访问页面,不需要手动打开。
(6) 发送 100 次并发请求进行预热
IntStream.range(0, 100).parallel().forEach(...)
这句的意思是:同时发起 100 次请求去访问 /warmup
接口。
这就像是在系统刚上线的时候,一口气模拟了 100 个人同时访问,让服务器提前适应一下流量压力,防止刚开始用的时候卡顿。
4) 启动性能监控
我们可以通过一个简单的监控类,来测量 Spring Boot 应用从启动到准备好所花的时间。这个类就像一个计时器,记录应用“从开机到能干活”用了多久。
@Component
public class StartupPerformanceMonitor {
private long startTime;
@EventListener
public void onStarting(ApplicationStartingEvent event) {
startTime = System.currentTimeMillis();
}
@EventListener
public void onReady(ApplicationReadyEvent event) {
long duration = System.currentTimeMillis() - startTime;
System.out.printf("应用启动完成,总耗时: %d ms%n", duration);
}
}
5) 数据库连接检查
这是一个用来检查数据库连接是否正常的 Java 类。它会在程序刚启动的时候自动运行。
@Component
public class DbConnectionChecker implements ApplicationListener<ApplicationStartedEvent> {
@Autowired
private DataSource dataSource;
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
try (Connection conn = dataSource.getConnection()) {
System.out.println("数据库连接测试成功");
} catch (SQLException e) {
throw new RuntimeException("数据库连接失败", e);
}
}
}
6) 服务注册
这个 Java 类叫 ServiceRegistryNotifier
,它的作用是在系统启动和关闭的时候,通知服务注册中心一些状态变化。
简单来说,它就像是一个“消息员”,负责在两个关键时刻传话:
- 当应用启动完成时,它会告诉服务注册中心:“我准备好了!”
- 当应用要关闭时,它会告诉服务注册中心:“我要下线了!”
它是怎么做到的呢?靠的是两个监听方法:
onReady()
方法会在应用启动完成后自动执行,去注册自己;onShutdown()
方法会在应用关闭前执行,把自己从注册中心取消掉。
这样做的好处是,别人就知道你这个服务什么时候可用、什么时候不可用了。
@Component
public class ServiceRegistryNotifier {
@EventListener
public void onReady(ApplicationReadyEvent event) {
registerWithServiceRegistry();
}
@EventListener
public void onShutdown(ContextClosedEvent event) {
unregisterFromServiceRegistry();
}
}
@EventListener
与 自定义 ApplicationListener
的深度对比
这两种事件监听机制在 Spring 框架中都用于响应 ApplicationEvent
,但在实现方式、功能特性和适用场景上存在显著差异:
1、本质区别
维度 | 自定义 ApplicationListener | @EventListener |
---|---|---|
实现方式 | 实现接口 ApplicationListener<特定事件> | 在任意方法添加注解 |
耦合度 | 与事件类型强耦合(需指定泛型) | 与事件类型解耦(通过参数类型识别) |
2、功能特性对比
1). 多事件监听能力
// @EventListener 支持监听多个事件类型
@EventListener({EventA.class, EventB.class})
public void handleMulti(AbstractEvent event) {
// 根据 event 实际类型处理
}
// ApplicationListener 需为每种事件单独实现
class ListenerA implements ApplicationListener<EventA> {...}
class ListenerB implements ApplicationListener<EventB> {...}
2). 条件化监听
// SpEL 条件过滤
@EventListener(condition = "#event.priority == 'HIGH'")
public void onHighPriority(AlertEvent event) {...}
// ApplicationListener 需在方法内手动判断
public void onApplicationEvent(AlertEvent event) {
if (!"HIGH".equals(event.getPriority())) return;
...
}
3). 异步执行支持
// 注解方式结合 @Async
@Async
@EventListener
public void asyncHandle(LogEvent event) {
// 在独立线程执行
}
// 接口实现需手动编码线程池
public void onApplicationEvent(LogEvent event) {
executor.submit(() -> process(event));
}
4). 事件发布链
// 返回新事件自动发布
@EventListener
public NewEvent handle(OriginEvent event) {
return new NewEvent(); // 自动发布 NewEvent
}
3、执行性能对比
指标 | ApplicationListener | @EventListener |
---|---|---|
注册耗时 | 低(启动时直接加载) | 较高(需扫描代理类) |
运行时效率 | 直接调用(invokevirtual) | 反射/CGLIB 代理调用(invoke) |
内存占用 | 每个事件类型独立实例 | 共享 Bean + 方法映射 |
实测数据 | 100万次调用 ≈ 220ms | 100万次调用 ≈ 480ms (无AOP时) |
注:开启
-noverify
JVM 参数可使注解方式性能提升 40%
4、适用场景推荐
方案 | 最佳使用场景 | 典型案例 |
---|---|---|
自定义 ApplicationListener | 1. 需要精确控制事件处理顺序 2. 高频事件(>1000/sec) 3. 早期版本兼容(Spring 3.x) | ContextRefreshedEvent 系统初始化 |
@EventListener | 1. 快速原型开发 2. 多事件联合处理 3. 需要条件过滤/异步执行 4. Spring Boot 应用 | @TransactionalEventListener 事务边界事件 |
Spring Boot 启动扩展点
Spring Boot 启动过程中,在不同的阶段可以插入自定义逻辑,这些插入点叫做“扩展点”。
也就是说, 可以在 Spring Boot 启动的不同时间点,加一些自己的代码来影响启动过程。
Spring Boot启动,根据不同介入阶段有哪些扩展点?
如下图:
1、扩展点分类
以下是 Spring Boot 启动过程中主要扩展点的分类表格,包含触发阶段、执行时机和典型用途:
分类 | 扩展点名称 | 触发阶段 | 典型用途 | 注册方式 |
---|---|---|---|---|
环境配置扩展点 | BootstrapRegistryInitializer | 最早阶段(Main方法执行前) | 注册引导阶段单例对象 | META-INF/spring.factories |
EnvironmentPostProcessor | 环境准备阶段(创建Environment后) | 修改环境配置(如解密属性) | META-INF/spring.factories | |
上下文控制扩展点 | ApplicationContextInitializer | 上下文创建后(Bean加载前) | 定制ApplicationContext(如设置活跃Profile) | SpringApplication.addInitializers() 或 META-INF/spring.factories |
BeanDefinitionRegistryPostProcessor | Bean定义注册阶段(ConfigurationClass处理阶段) | 动态注册Bean定义 | @Component 或编程注册 | |
BeanFactoryPostProcessor | Bean工厂准备阶段(所有Bean定义加载后) | 修改Bean定义(如替换实现类) | @Component 或编程注册 | |
Bean生命周期扩展点 | BeanPostProcessor | Bean实例化前后(每个Bean的初始化阶段) | AOP代理创建、属性注入监控 | @Component 或编程注册 |
SmartInitializingSingleton | 所有单例Bean初始化完成后 | 缓存预热、启动检查 | 实现接口的Bean会自动生效 | |
应用生命周期扩展点 | SpringApplicationRunListener | 贯穿整个启动过程(7个关键事件) | 启动过程监控、自定义事件处理 | META-INF/spring.factories |
ApplicationRunner | 所有初始化完成后(接收结构化参数) | 业务初始化、启动任务 | @Component | |
CommandLineRunner | 所有初始化完成后(接收原始命令行参数) | 命令行参数处理 | @Component | |
事件监听扩展点 | ApplicationListener | 任意事件发布时 | 响应特定事件(如上下文刷新) | @Component 或 META-INF/spring.factories |
@EventListener | 指定事件触发时 | 细粒度事件处理 | 注解在方法上 | |
条件化控制扩展点 | @Conditional系列 | 配置类处理阶段 | 条件化Bean注册 | 与@Bean 或@Configuration 配合使用 |
2、 Spring Boot 事件类扩展点 和 Spring Boot 启动中的事件机制 有何关系
Spring Boot 的 启动事件机制 与 启动扩展点 本质上是协同工作的互补机制,共同构成 Spring Boot 的 可扩展启动架构。
Spring Boot 的 启动事件机制 与 启动扩展点 本 本质区别与互补性:
维度 | 启动事件 | 启动扩展点 | 互补性说明 |
---|---|---|---|
定位 | 状态变更的广播系统 | 可插入的业务逻辑单元 | 扩展点依赖事件获取执行时机 |
实现形式 | ApplicationEvent 子类 | 接口/注解(如ApplicationRunner) | 事件是扩展点的触发信号源 |
执行控制 | 全局广播(所有监听器都会收到) | 精确介入特定扩展接口 | 事件提供全局坐标,扩展点精准执行 |
典型生命周期 | starting → ready 的全过程 | 特定阶段切入(如所有runner在started后执行) | 扩展点聚焦关键阶段 |
3、核心扩展点详解
1). BootstrapRegistryInitializer
源码位置:org.springframework.boot.BootstrapRegistryInitializer
触发时机:
// SpringApplication.java
private ConfigurableApplicationContext run(String... args) {
// 第一步执行
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// ...
}
private DefaultBootstrapContext createBootstrapContext() {
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
this.bootstrapRegistryInitializers.forEach(initializer ->
initializer.initialize(bootstrapContext)); // 关键执行点
return bootstrapContext;
}
使用方式:
// 实现类示例
public class MyBootstrapInitializer implements BootstrapRegistryInitializer {
@Override
public void initialize(BootstrapRegistry registry) {
registry.register(ConfigService.class, context -> {
return new ConfigService("http://config-center:8888");
});
}
}
// 注册方式(META-INF/spring.factories)
org.springframework.boot.BootstrapRegistryInitializer=com.example.MyBootstrapInitializer
典型场景:
- 配置中心客户端预初始化
- 早期监控组件注册
- 密钥管理服务初始化
2). EnvironmentPostProcessor
源码位置:org.springframework.boot.env.EnvironmentPostProcessor
触发流程:
// SpringApplication.java
private ConfigurableEnvironment prepareEnvironment(...) {
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment); // 触发事件
// ...
}
实现示例:
public class VaultConfigProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment env,
SpringApplication app) {
VaultClient vault = new VaultClient(env.getProperty("vault.endpoint"));
env.getPropertySources().addFirst(
new VaultPropertySource(vault.fetchSecrets()));
}
}
使用场景:
- 从Vault/Consul加载机密配置
- 配置文件解密
- 动态生成配置属性
3). ApplicationContextInitializer
源码位置:org.springframework.context.ApplicationContextInitializer
执行时机:
// SpringApplication.java
private void prepareContext(...) {
applyInitializers(context); // 关键调用
}
private void applyInitializers(ConfigurableApplicationContext context) {
for (ApplicationContextInitializer initializer : getInitializers()) {
initializer.initialize(context);
}
}
实战案例:
public class ClusterContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
if (context.getEnvironment().acceptsProfiles("cluster")) {
context.getBeanFactory().registerSingleton(
"clusterManager", new ClusterManager());
}
}
}
// 注册方式(application.properties)
context.initializer.classes=com.example.ClusterContextInitializer
适用场景:
- 根据环境动态注册Bean
- 上下文个性化配置
- 早期后处理器注册
4). BeanDefinitionRegistryPostProcessor
源码位置:org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
执行流程:
// PostProcessorRegistrationDelegate.java
public static void invokeBeanFactoryPostProcessors(...) {
invokeBeanDefinitionRegistryPostProcessors(priorityOrderedPostProcessors, registry);
// ...
}
动态注册示例:
public class DynamicRepositoryRegistrar implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
scanPackages("com.example.repositories").forEach(clazz -> {
String beanName = StringUtils.uncapitalize(clazz.getSimpleName());
registry.registerBeanDefinition(beanName,
BeanDefinitionBuilder.rootBeanDefinition(clazz)
.setScope(BeanDefinition.SCOPE_SINGLETON)
.getBeanDefinition());
});
}
}
典型应用:
- 自动注册DAO/Repository
- 插件系统实现
- 条件化Bean注册
5). BeanPostProcessor
源码位置:org.springframework.beans.factory.config.BeanPostProcessor
执行机制:
// AbstractAutowireCapableBeanFactory.java
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
// ...
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
// 执行初始化方法
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
return wrappedBean;
}
AOP代理示例:
public class TracingPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean.getClass().isAnnotationPresent(Traceable.class)) {
return Proxy.newProxyInstance(
bean.getClass().getClassLoader(),
bean.getClass().getInterfaces(),
new TracingInvocationHandler(bean));
}
return bean;
}
}
使用场景:
- AOP代理创建
- 性能监控埋点
- 动态属性注入
6). SmartInitializingSingleton
源码位置:org.springframework.beans.factory.SmartInitializingSingleton
触发时机:
// DefaultListableBeanFactory.java
public void preInstantiateSingletons() throws BeansException {
// ...
if (bean instanceof SmartInitializingSingleton) {
((SmartInitializingSingleton) bean).afterSingletonsInstantiated();
}
}
缓存预热示例:
@Component
public class CacheWarmer implements SmartInitializingSingleton {
@Autowired
private ProductRepository repository;
@Override
public void afterSingletonsInstantiated() {
repository.findAll().forEach(product -> {
CacheManager.put(product.getId(), product);
});
}
}
适用场景:
- 缓存预热
- 连接池初始化
- 静态数据加载
7). ApplicationRunner/CommandLineRunner
源码位置:
org.springframework.boot.ApplicationRunner
org.springframework.boot.CommandLineRunner
执行流程:
// SpringApplication.java
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
// 执行所有Runner
}
使用对比:
特性 | ApplicationRunner | CommandLineRunner |
---|---|---|
参数类型 | 结构化参数(ApplicationArguments) | 原始命令行参数(String[]) |
适用场景 | 需要解析参数的初始化逻辑 | 简单命令行处理 |
执行顺序控制 | 通过@Order注解 | 通过@Order注解 |
示例代码:
@Component
@Order(1)
public class DbInitializer implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
if (args.containsOption("init-db")) {
initializeDatabase();
}
}
}
@Component
@Order(2)
public class CacheInitializer implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
CacheManager.init();
}
}
3、如何选择扩展点
(1)、常见场景扩展点选择
需求场景 | 首选扩展点 | 备选方案 | 不适用方案 |
---|---|---|---|
最早期的初始化 | BootstrapRegistryInitializer | - | 其他扩展点都太晚 |
修改环境配置 | EnvironmentPostProcessor | ApplicationContextInitializer | BeanPostProcessor |
动态注册Bean | BeanDefinitionRegistryPostProcessor | @Configuration+@Bean | BeanFactoryPostProcessor |
Bean实例增强 | BeanPostProcessor | AOP | ApplicationListener |
所有Bean就绪后操作 | SmartInitializingSingleton | @EventListener(ContextRefreshedEvent) | ApplicationRunner |
启动参数处理 | ApplicationRunner | CommandLineRunner | @PostConstruct |
应用状态变更响应 | @EventListener | ApplicationListener接口 | BeanPostProcessor |
条件化配置 | @Conditional系列注解 | BeanDefinitionRegistryPostProcessor | EnvironmentPostProcessor |
BeanFactoryPostProcessor
(2)、典型场景
场景1:配置加密解密
最佳选择:EnvironmentPostProcessor
public class DecryptProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication app) {
env.getPropertySources().replace("encrypted",
new DecryptedPropertySource(env.getPropertySources().get("encrypted")));
}
}
场景2:动态数据源注册
最佳选择:BeanDefinitionRegistryPostProcessor
+ @EventListener
public class DataSourceRegistrar implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("dynamicDS", ...);
}
}
@Component
public class DataSourceInitializer {
@EventListener(ContextRefreshedEvent.class)
public void initDataSource() {
// 数据源后续初始化
}
}
场景3:接口性能监控
最佳选择:BeanPostProcessor
public class MonitoringPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof RestController) {
return Proxy.newProxyInstance(...); // 创建监控代理
}
return bean;
}
}
3、选择策略
(1) 阶段优先原则:
-
选择能满足需求的最晚阶段扩展点
-
例如:能用
ApplicationRunner
就不选SmartInitializingSingleton
(2) 最小侵入原则:
- 优先选择注解方式(如
@EventListener
)而非接口实现 - 优先使用框架提供的高级抽象(如
@Conditional
)
(3) 单一职责原则:
- 每个扩展点只处理一类问题
- 复杂逻辑拆分为多个扩展点协同工作
(4) 显式优于隐式:
- 明确指定执行顺序(
@Order
) - 避免依赖不确定的默认行为