阿里面试:SpringBoot启动时, 如何执行扩展代码?你们项目 SpringBoot 进行过 哪些 扩展?

本文 的 原文 地址

原始的内容,请参考 本文 的 原文 地址

本文 的 原文 地址

尼恩说在前面

在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就绪后执行的逻辑
@PostConstructBean初始化完成后仅限于单个Bean的生命周期
SpringApplicationRunListener贯穿整个启动过程需要实现多个方法,适合框架级扩展
BeanPostProcessor每个Bean初始化前后粒度最细,可以干预每个Bean的创建过程

2、扩展点的执行时机

spring boot 中这么多扩展点,源码中的触发点在那,在那个时机执行的?

如何选择合适的扩展点执行特定代码呢?

Spring Boot扩展点分类 与 选择

如果对于spring boot扩展点做大致分类,可以分为两类

  • 事件类 扩展点: 基于 观察者模式 的事件类扩展点 , 被动接收模式
  • 非事件类扩展点: 基于 模版模式 的非事件类扩展点(比如后置处理器 BeanFactoryPostProcessor,CommandLineRunner等) ,主动调用模式

1、扩展点分类统计

根据Spring Boot官方文档及源码分析,我将核心扩展点分类统计如下:

类型扩展点名称/机制数量占比
事件类ApplicationEvent 子类12+75%
〰️@EventListener 监听机制-
非事件类ApplicationRunner125%
〰️CommandLineRunner1
〰️SpringApplicationRunListener1
〰️ApplicationContextInitializer1
〰️BeanFactoryPostProcessor1
总计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 个核心事件及其触发时机和作用:

序号事件类型触发时机核心作用是否可修改容器
1ApplicationStartingEventSpringApplication.run() 执行后立即触发最早介入点,注册自定义监听器
2BootstrapContextInitializedEvent引导上下文(BootstrapContext)初始化后配置加密/密钥管理的最佳时机
3ApplicationEnvironmentPreparedEvent环境对象(Environment)准备完成,但尚未加载配置文件动态修改配置的最后机会(如:添加 PropertySource)
4ApplicationContextInitializedEventApplicationContext 初始化完成,但尚未加载 Bean定义执行早期容器定制(如设置活动 profiles)
5ApplicationPreparedEventBean 定义加载完成,但实例化之前最后修改 Bean 定义的机会(添加自定义 BeanPostProcessor)
6ContextRefreshedEvent上下文完全刷新后触发(Spring Framework 原生事件)执行需要完整上下文的操作⚠️ 风险操作
7WebServerInitializedEvent内嵌 Web 服务器(Tomcat/Jetty)启动完成获取服务器端口等运行时信息
8ApplicationStartedEvent上下文已刷新,但未触发 ApplicationRunner资源预热准备(如缓存加载)⚠️ 只读访问
9AvailabilityChangeEvent (Liveness)应用进入活动状态(LivenessState.CORRECT)Kubernetes 存活探针准备
10ApplicationReadyEvent所有 ApplicationRunner 执行完毕业务初始化安全点(可安全访问服务)⚠️ 只读访问
11AvailabilityChangeEvent (Readiness)应用就绪可接收流量(ReadinessState.ACCEPTING_TRAFFIC)Kubernetes 就绪探针触发
12ApplicationFailedEvent启动过程任何阶段失败时触发失败处理(如发送告警、记录诊断日志)

注:Spring Boot 2.x 中 ApplicationReadyEventApplicationStartedEvent 合并为单一事件

(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 构造)

image-20250619194648051

程序入口执行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

image-20250619211958901

接下来执行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)

image-20250619212919647

继续执行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

image-20250619220054471


/**
 * 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

image-20250620105841920


/**
 * 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) 策略模式:

  • ApplicationRunnerCommandLineRunner提供不同的参数处理策略
  • 统一通过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,它的作用就像是个“名单读取器”,根据名字去找到这些监听器。

监听器通过 SpringFactoriesLoaderMETA-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定义
ApplicationPreparedEventBean定义加载完成refreshContext()动态注册Bean定义、修改Bean属性值、准备AOP代理
ContextRefreshedEvent上下文刷新完成AbstractApplicationContext.refresh()缓存预热、初始化静态数据、执行一次性计算任务
WebServerInitializedEventWeb服务器就绪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万次调用 ≈ 220ms100万次调用 ≈ 480ms (无AOP时)

注:开启 -noverify JVM 参数可使注解方式性能提升 40%

4、适用场景推荐
方案最佳使用场景典型案例
自定义 ApplicationListener1. 需要精确控制事件处理顺序 2. 高频事件(>1000/sec) 3. 早期版本兼容(Spring 3.x)ContextRefreshedEvent 系统初始化
@EventListener1. 快速原型开发 2. 多事件联合处理 3. 需要条件过滤/异步执行 4. Spring Boot 应用@TransactionalEventListener 事务边界事件

Spring Boot 启动扩展点

Spring Boot 启动过程中,在不同的阶段可以插入自定义逻辑,这些插入点叫做“扩展点”。

也就是说, 可以在 Spring Boot 启动的不同时间点,加一些自己的代码来影响启动过程。

Spring Boot启动,根据不同介入阶段有哪些扩展点?

如下图:

image-20250620180901239

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
BeanDefinitionRegistryPostProcessorBean定义注册阶段(ConfigurationClass处理阶段)动态注册Bean定义@Component 或编程注册
BeanFactoryPostProcessorBean工厂准备阶段(所有Bean定义加载后)修改Bean定义(如替换实现类)@Component 或编程注册
Bean生命周期扩展点BeanPostProcessorBean实例化前后(每个Bean的初始化阶段)AOP代理创建、属性注入监控@Component 或编程注册
SmartInitializingSingleton所有单例Bean初始化完成后缓存预热、启动检查实现接口的Bean会自动生效
应用生命周期扩展点SpringApplicationRunListener贯穿整个启动过程(7个关键事件)启动过程监控、自定义事件处理META-INF/spring.factories
ApplicationRunner所有初始化完成后(接收结构化参数)业务初始化、启动任务@Component
CommandLineRunner所有初始化完成后(接收原始命令行参数)命令行参数处理@Component
事件监听扩展点ApplicationListener任意事件发布时响应特定事件(如上下文刷新)@ComponentMETA-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
}

使用对比

特性ApplicationRunnerCommandLineRunner
参数类型结构化参数(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-其他扩展点都太晚
修改环境配置EnvironmentPostProcessorApplicationContextInitializerBeanPostProcessor
动态注册BeanBeanDefinitionRegistryPostProcessor@Configuration+@BeanBeanFactoryPostProcessor
Bean实例增强BeanPostProcessorAOPApplicationListener
所有Bean就绪后操作SmartInitializingSingleton@EventListener(ContextRefreshedEvent)ApplicationRunner
启动参数处理ApplicationRunnerCommandLineRunner@PostConstruct
应用状态变更响应@EventListenerApplicationListener接口BeanPostProcessor
条件化配置@Conditional系列注解BeanDefinitionRegistryPostProcessorEnvironmentPostProcessor

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
  • 避免依赖不确定的默认行为

扩展点综合应用案例:多租户系统

原始的内容,请参考 本文 的 原文 地址

本文 的 原文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值