阿里面试:SpringBoot启动时, 如何执行扩展代码?你们项目 SpringBoot 进行过 哪些 扩展?_spring-parallel-initializer
本文 的 原文 地址
原始的内容,请参考 本文 的 原文 地址
本文 的 原文 地址
尼恩说在前面
在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
@PostConstruct
SpringApplicationRunListener
BeanPostProcessor
2、扩展点的执行时机
spring boot 中这么多扩展点,源码中的触发点在那,在那个时机执行的?
如何选择合适的扩展点执行特定代码呢?
Spring Boot扩展点分类 与 选择
如果对于spring boot扩展点做大致分类,可以分为两类
- 事件类 扩展点: 基于 观察者模式 的事件类扩展点 , 被动接收模式
- 非事件类扩展点: 基于 模版模式 的非事件类扩展点(比如后置处理器
BeanFactoryPostProcessor
,CommandLineRunner等) ,主动调用模式
1、扩展点分类统计
根据Spring Boot官方文档及源码分析,我将核心扩展点分类统计如下:
ApplicationEvent
子类@EventListener
监听机制ApplicationRunner
CommandLineRunner
SpringApplicationRunListener
ApplicationContextInitializer
BeanFactoryPostProcessor
尼恩社群,一个塔尖、硬核 技术 研究圈 , 我们对 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 个核心事件及其触发时机和作用:
ApplicationStartingEvent
SpringApplication.run()
执行后立即触发BootstrapContextInitializedEvent
ApplicationEnvironmentPreparedEvent
ApplicationContextInitializedEvent
ApplicationPreparedEvent
ContextRefreshedEvent
WebServerInitializedEvent
ApplicationStartedEvent
AvailabilityChangeEvent
(Liveness)ApplicationReadyEvent
AvailabilityChangeEvent
(Readiness)ApplicationFailedEvent
注:Spring Boot 2.x 中
ApplicationReadyEvent
和ApplicationStartedEvent
合并为单一事件
(2) 非事件类扩展点的选择 场景(20%特殊情况)
CommandLineRunner
ApplicationRunner
SpringApplicationRunListener
BeanDefinitionRegistryPostProcessor
为啥 SpringApplicationRunListener
的属于非事件类扩展点?
SpringApplicationRunListener
是 Spring Boot 启动架构中的核心非事件类扩展点,尽管其名称包含 “Listener”,但在扩展机制分类中属于非事件类扩展点。
1、分类依据
1). 实现机制对比
SpringApplicationRunListener
ApplicationListener
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.factoriesorg.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创建:
// 创建并配置BeanFactoryConfigurableListableBeanFactory 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) 耗时统计维度:
(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) 健康检查预热:
@Componentpublic class CacheWarmer implements CommandLineRunner { @Override public void run(String... args) { // 预热缓存 }}
(3) 启动参数处理:
@Beanpublic 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.SpringApplicationpublic ConfigurableApplicationContext run(String... args) { // ... SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(bootstrapContext, this.mainApplicationClass); // 发布ApplicationStartingEvent // ...}
EventPublishingRunListener
实现了SpringApplicationRunListener 是实际的事件转发器
这些事件具体是怎么发出去的呢?
靠的是一个叫 EventPublishingRunListener
的类。它实现了 SpringApplicationRunListener
接口,是真正负责把事件广播出去的那个“通讯员”。
// org.springframework.boot.context.event.EventPublishingRunListenerpublic 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.SpringApplicationprivate SpringApplicationRunListeners getRunListeners(String[] args) { Class[] types = { SpringApplication.class, String[].class }; List 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 启动过程就像一步步准备食材、开火、炒菜。
每个事件就像是一个关键步骤,比如洗菜、切菜、炒熟、装盘,每一步都可以插手去做点事。
SpringApplicationRunListeners.starting()
prepareEnvironment()
prepareContext()
refreshContext()
前AbstractApplicationContext.refresh()
WebServerManager.initialize()
afterRefresh()
后EventPublishingRunListener.started()
callRunners()
后EventPublishingRunListener.ready()
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 { @Override public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { ConfigurableEnvironment env = event.getEnvironment(); for (PropertySource ps : env.getPropertySources()) { if (ps instanceof EnumerablePropertySource) { Map 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 { @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.SpringApplicationprivate 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
:适用于所有服务就绪后的操作
@Componentpublic class ClusterCoordinator { @EventListener public void onStarted(ApplicationStartedEvent event) { // 抢先注册节点 registerNode(); } @EventListener public void onReady(ApplicationReadyEvent event) { // 等待所有服务就绪后开始选举 startLeaderElection(); }}
4) AvailabilityChangeEvent 机制
状态转换逻辑:
// org.springframework.boot.availability.AvailabilityChangeEventpublic 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 { @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 { @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\")@Componentpublic 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 应用从启动到准备好所花的时间。这个类就像一个计时器,记录应用“从开机到能干活”用了多久。
@Componentpublic 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 类。它会在程序刚启动的时候自动运行。
@Componentpublic class DbConnectionChecker implements ApplicationListener { @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()
方法会在应用关闭前执行,把自己从注册中心取消掉。
这样做的好处是,别人就知道你这个服务什么时候可用、什么时候不可用了。
@Componentpublic 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 {...}class ListenerB implements ApplicationListener {...}
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@EventListenerpublic void asyncHandle(LogEvent event) { // 在独立线程执行}// 接口实现需手动编码线程池public void onApplicationEvent(LogEvent event) { executor.submit(() -> process(event));}
4). 事件发布链
// 返回新事件自动发布@EventListenerpublic NewEvent handle(OriginEvent event) { return new NewEvent(); // 自动发布 NewEvent}
3、执行性能对比
ApplicationListener
@EventListener
注:开启
-noverify
JVM 参数可使注解方式性能提升 40%
4、适用场景推荐
ContextRefreshedEvent
系统初始化@TransactionalEventListener
事务边界事件Spring Boot 启动扩展点
Spring Boot 启动过程中,在不同的阶段可以插入自定义逻辑,这些插入点叫做“扩展点”。
也就是说, 可以在 Spring Boot 启动的不同时间点,加一些自己的代码来影响启动过程。
Spring Boot启动,根据不同介入阶段有哪些扩展点?
如下图:
1、扩展点分类
以下是 Spring Boot 启动过程中主要扩展点的分类表格,包含触发阶段、执行时机和典型用途:
META-INF/spring.factories
META-INF/spring.factories
SpringApplication.addInitializers()
或 META-INF/spring.factories
@Component
或编程注册@Component
或编程注册@Component
或编程注册META-INF/spring.factories
@Component
@Component
@Component
或 META-INF/spring.factories
@Bean
或@Configuration
配合使用2、 Spring Boot 事件类扩展点 和 Spring Boot 启动中的事件机制 有何关系
Spring Boot 的 启动事件机制 与 启动扩展点 本质上是协同工作的互补机制,共同构成 Spring Boot 的 可扩展启动架构。
Spring Boot 的 启动事件机制 与 启动扩展点 本 本质区别与互补性:
3、核心扩展点详解
1). BootstrapRegistryInitializer
源码位置:org.springframework.boot.BootstrapRegistryInitializer
触发时机:
// SpringApplication.javaprivate 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.javaprivate 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.javaprivate void prepareContext(...) { applyInitializers(context); // 关键调用}private void applyInitializers(ConfigurableApplicationContext context) { for (ApplicationContextInitializer initializer : getInitializers()) { initializer.initialize(context); }}
实战案例:
public class ClusterContextInitializer implements ApplicationContextInitializer { @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.javapublic 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.javaprotected 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.javapublic void preInstantiateSingletons() throws BeansException { // ... if (bean instanceof SmartInitializingSingleton) { ((SmartInitializingSingleton) bean).afterSingletonsInstantiated(); }}
缓存预热示例:
@Componentpublic 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.javaprivate void callRunners(ApplicationContext context, ApplicationArguments args) { List runners = new ArrayList(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); AnnotationAwareOrderComparator.sort(runners); // 执行所有Runner}
使用对比:
示例代码:
@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)、常见场景扩展点选择
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\", ...); }}@Componentpublic 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
) - 避免依赖不确定的默认行为
扩展点综合应用案例:多租户系统
原始的内容,请参考 本文 的 原文 地址
本文 的 原文