程序员scholar 程序员scholar
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
首页
  • Java 基础

    • JavaSE
    • JavaIO
    • JavaAPI速查
  • Java 高级

    • JUC
    • JVM
    • Java新特性
    • 设计模式
  • Web 开发

    • Servlet
    • Java网络编程
  • Web 标准

    • HTML
    • CSS
    • JavaScript
  • 前端框架

    • Vue2
    • Vue3
    • Vue3 + TS
    • 微信小程序
    • uni-app
  • 工具与库

    • jQuery
    • Ajax
    • Axios
    • Webpack
    • Vuex
    • WebSocket
    • 第三方登录
  • 后端与语言扩展

    • ES6
    • Typescript
    • node.js
  • Element-UI
  • Apache ECharts
  • 数据结构
  • HTTP协议
  • HTTPS协议
  • 计算机网络
  • Linux常用命令
  • Windows常用命令
  • SQL数据库

    • MySQL
    • MySQL速查
  • NoSQL数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • RabbitMQ
  • 服务器

    • Nginx
  • Spring框架

    • Spring6
    • SpringMVC
    • SpringBoot
    • SpringSecurity
  • SpringCould微服务

    • SpringCloud基础
    • 微服务之DDD架构思想
  • 日常必备

    • 开发常用工具包
    • Hutoll工具包
    • IDEA常用配置
    • 开发笔记
    • 日常记录
    • 项目部署
    • 网站导航
    • 产品学习
    • 英语学习
  • 代码管理

    • Maven
    • Git教程
    • Git小乌龟教程
  • 运维工具

    • Docker
    • Jenkins
    • Kubernetes
  • 算法笔记

    • 算法思想
    • 刷题笔记
  • 面试问题常见

    • 十大经典排序算法
    • 面试常见问题集锦
关于
GitHub (opens new window)
npm

(进入注册为作者充电)

  • Spring Boot

    • Spring Boot - 自动配置
    • Spring Boot - 自定义starter
    • Spring Boot - 配置文件
    • Spring Boot - 自定义SpringApplication
    • Spring Boot - 生命周期与事件
      • 1. 前言
      • 2. 生命周期监听
        • 1. 创建spring.factories文件
        • 2. 注册SpringApplicationRunListener
        • 3. 确保监听器有正确的构造函数
        • 4. 重新监听器对应的方法
        • 5. Spring Boot初始化流程
        • 5.1. 引导(Bootstrap)
        • 5.2 启动项目
        • 5.3 运行项目
      • 3.SpringApplication的run方法源码
      • 4. 事件触发时机
        • BootstrapRegistryInitializer
        • ApplicationContextInitializer
        • ApplicationListener
        • SpringApplicationRunListener
        • ApplicationRunner
        • CommandLineRunner
        • 最佳实战
      • 5. 事件完整触发流程
        • 1. ApplicationStartingEvent
        • 2. ApplicationEnvironmentPreparedEvent
        • 3. ApplicationContextInitializedEvent
        • 4. ApplicationPreparedEvent
        • 5. ApplicationStartedEvent
        • 6. AvailabilityChangeEvent(LivenessState.CORRECT)
        • 7. ApplicationReadyEvent
        • 8. AvailabilityChangeEvent(ReadinessState.ACCEPTING_TRAFFIC)
        • 9. ApplicationFailedEvent
        • 10. 如何确保监听器执行顺序
        • 11. 使用事件监听的优势
    • Spring Boot - 事件驱动
    • Spring Boot - Bean 加载方式
    • Spring Boot - 容器资源感知与获取
    • Spring Boot - 定时任务
    • Spring Boot - 异步任务
    • Spring Boot - 内置日志
    • Spring Boot - 函数式 Web
    • Spring Boot - 响应式远程调用
    • Spring Boot - 接口文档
    • Spring Boot - 单元测试
    • Spring Boot - 内容协商
    • Spring Boot - 参数校验
    • Spring Boot - HTTP客户端工具
    • Spring Boot - 控制器请求映射
    • Spring Boot - 请求参数接收
    • Spring Boot - 通用响应类
    • Spring Boot - 全局异常处理
    • Spring Boot - 整合Druid
    • Spring Boot - 整合Thymeleaf
    • Spring Boot - 国际化实现
    • Spring Boot - 自定义注解
  • Spring高级
  • Spring Boot
scholar
2023-10-29
目录

Spring Boot - 生命周期与事件

序言

本节详解SpringBoot初始化流程,内容会涉及源码解析,建议看视频学习会更容易理解。

视频地址:https://www.bilibili.com/video/BV1Es4y1q7Bf?p=59 (opens new window),从 59 看到 64 集。

# 1. 前言

Spring Boot 初始化的时候会有生命周期,在到达这些生命周期的时候,就会触发一个回调,我们可以实现这些回调,在这个生命周期的回调实现自己的业务,类似于 Vue 的生命周期。

除了生命周期,还有事件,生命周期是一个完整的阶段,而事件是这个阶段的一个行为。因此一个生命周期至少有一个事件以上。

我们可以监听某个生命周期,也可以监听某个事件,事件相比较生命周期更为具体。就像人有出生、学习、进入社会、结婚、生子、退休、死亡等生命周期,而这些生命周期会有很多的事件发生。

# 2. 生命周期监听

了解 SpringBoot 初始化的生命周期,我们可以实现 SpringApplicationRunListener 接口的所有方法,这些方法就是 Spring Boot 的生命周期阶段回调。至于这些方法在哪里调用,可以在 SpringApplicaiton 的 run 方法里查看。

要想让自定义的SpringApplicationRunListener生效,需要按照Spring Boot的要求将它注册到应用中。Spring Boot使用spring.factories文件来自动发现和注册SpringApplicationRunListener实例。以下详细步骤:

# 1. 创建spring.factories文件

首先,确保在你的资源目录下(src/main/resources)有一个META-INF目录。在META-INF目录中创建或编辑spring.factories文件。

# 2. 注册SpringApplicationRunListener

在spring.factories文件中,添加以下行来注册您的监听器:

org.springframework.boot.SpringApplicationRunListener=com.example.MyListener
1

这里的com.example.MyListener是自定义的监听器的完全限定类名。

# 3. 确保监听器有正确的构造函数

Spring Boot期望SpringApplicationRunListener有一个特定的构造函数签名,接受一个SpringApplication实例和一个字符串数组(args)作为参数。确保MyListener类包含这样的构造函数:

public MyListener(SpringApplication application, String[] args) {
    // 构造函数的实现
}
1
2
3

完成上述步骤后,Spring Boot应用启动时,Spring Boot会自动发现并注册自定义的MyListener,自定义监听器将在应用的生命周期中的相应阶段被调用。

# 4. 重新监听器对应的方法

public class MyListener implements SpringApplicationRunListener {

    public MyListener(SpringApplication application, String[] args) {
        // 构造函数的实现
    }

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println("starting===项目正在启动");
        SpringApplicationRunListener.super.starting(bootstrapContext);
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        System.out.println("environmentPrepared===环境准备完成");
        SpringApplicationRunListener.super.environmentPrepared(bootstrapContext, environment);
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("contextPrepared===容器加载前");
        SpringApplicationRunListener.super.contextPrepared(context);
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("contextLoaded===容器加载完成,但是 Bean 还没有刷新到容器");
        SpringApplicationRunListener.super.contextLoaded(context);
    }

    @Override
    public void started(ConfigurableApplicationContext context) {
        System.out.println("started===环境启动完成,所有 Bean 创建完成");
        SpringApplicationRunListener.super.started(context);
    }

    @Override
    public void running(ConfigurableApplicationContext context) {
        System.out.println("running===项目准备就绪");
        SpringApplicationRunListener.super.running(context);
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("failed===项目启动失败");
        SpringApplicationRunListener.super.failed(context, exception);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

image-20240331023128414

# 5. Spring Boot初始化流程

image-20240330160801862

# 5.1. 引导(Bootstrap)

引导阶段是Spring Boot启动过程的开始,主要关注于提供必要的配置信息和环境准备。

  • starting:
    • 触发时机:这是Spring Boot启动流程中的第一步,当SpringApplication的run方法被调用时触发。
    • 作用:标志着应用启动的开始。在这个阶段,BootstrapContext被创建,用于引导整个项目的启动过程。
  • environmentPrepared:
    • 触发时机:在这一步,应用的环境已经准备好了,这包括将启动参数、应用参数等绑定到环境变量中。
    • 作用:这个阶段主要是准备配置环境,但此时ApplicationContext(IoC容器)还没有被创建。

# 5.2 启动项目

启动项目阶段主要涉及到ApplicationContext的创建和配置,以及Bean的加载但未初始化。

  • contextPrepared:
    • 触发时机:在此阶段,ApplicationContext已经被创建并准备好,但是应用的主配置类(sources)还没有被加载。
    • 作用:主要是对IoC容器进行准备工作,此时IoC容器已经创建但还处于空壳状态,组件尚未创建。
  • contextLoaded:
    • 触发时机:到这一步,主配置类已经被加载到ApplicationContext中,但容器尚未被刷新,也就是说Bean还没有被实例化。
    • 作用:此阶段完成了配置类的加载,为Bean的创建做好准备。
  • started:
    • 触发时机:此时,ApplicationContext已被刷新,所有的Bean都已经创建并初始化完毕,但是应用级别的runner(如CommandLineRunner和ApplicationRunner)还没有被调用。
    • 作用:标志着所有的Bean都已准备就绪,但应用还未完全就绪,因为runner尚未执行。
  • ready(running):
    • 触发时机:这一步是在ApplicationContext刷新并且所有的runner都被调用之后。
    • 作用:表示应用已经完全准备好,可以开始接收请求了。在Spring Boot中,这个阶段通常对应于ApplicationReadyEvent的发布。

# 5.3 运行项目

  • 在所有上述步骤都正确执行之后,应用进入运行状态,这意味着Spring Boot应用已经完全启动并且处于运行中的状态。此时,应用已经加载了所有的配置、创建并初始化了所有的Bean,并执行了所有的runner,准备好接收和处理用户请求。

# 3.SpringApplication的run方法源码







 








 
 






 








 






 













 






 





 











public ConfigurableApplicationContext run(String... args) {
    // 启动计时,用于计算启动耗时
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();

    // 创建引导上下文,用于Spring Boot应用的早期阶段
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();

    // 初始化上下文变量
    ConfigurableApplicationContext context = null;

    // 配置Headless属性。这通常影响GUI应用。
    configureHeadlessProperty();

    // 获取应用的运行监听器,这些监听器可以响应应用的生命周期事件
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting(bootstrapContext, this.mainApplicationClass);

    try {
        // 封装命令行参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

        // 准备环境变量,包括配置文件和命令行参数等
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);

        // 配置忽略JavaBeans的某些属性信息
        configureIgnoreBeanInfo(environment);

        // 打印Banner
        Banner printedBanner = printBanner(environment);

        // 创建ApplicationContext,即Spring的IoC容器
        context = createApplicationContext();
        context.setApplicationStartup(this.applicationStartup);

        // 准备ApplicationContext,包括设置环境变量,注册Bean等
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);

        // 刷新ApplicationContext,完成Bean的创建和初始化
        refreshContext(context);

        // 在ApplicationContext刷新之后,但在命令行Runner之前执行的自定义逻辑
        afterRefresh(context, applicationArguments);

        // 停止计时
        stopWatch.stop();

        // 如果启动信息日志被启用,则记录启动信息
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }

        // 触发ApplicationContext已启动的事件
        listeners.started(context);

        // 调用所有的CommandLineRunner和ApplicationRunner
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        // 处理启动过程中的失败,包括关闭ApplicationContext
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 触发应用已在运行的事件
        listeners.running(context);
    }
    catch (Throwable ex) {
        // 如果在运行过程中发生异常,同样需要处理失败
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }

    // 返回已启动并在运行的ApplicationContext
    return context;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

这个方法大致描述了Spring Boot启动过程中的关键步骤,包括启动计时、准备环境、创建应用上下文、刷新上下文、处理启动成功或失败等。通过这个流程,Spring Boot确保了应用的正确配置和初始化,最终达到可运行状态。

# 4. 事件触发时机

我们可以自定义类实现这些监听器,然后添加进去,这样在到达对应的事件后,就会自动触发方法,于是我们可以在这些方法里进行业务的处理。

下面是new SpringApplication的源码解析,它负责初始化Spring Boot应用的一些关键组成部分:















 


 


 





public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    // 资源加载器,可以是null。如果提供,它将用于加载Spring Boot应用的资源。
    this.resourceLoader = resourceLoader;
    
    // 断言传入的主配置类(primarySources)不为null,这些类通常带有@SpringBootApplication或@Configuration注解。
    Assert.notNull(primarySources, "PrimarySources must not be null");
    
    // 初始化一个LinkedHashSet来存储主配置类,保证了添加顺序并去重。
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    
    // 推断Web应用类型(SERVLET、REACTIVE或NONE),这基于类路径上的类来决定应用的类型。
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    
    // 通过Spring的Factories机制加载所有Bootstrapper实例,Bootstrapper是用于应用启动早期阶段的组件。
    this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
    
    // 设置应用上下文初始化器,这些初始化器在Spring应用上下文刷新之前调用,用于对上下文进行编程式配置。
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    
    // 设置应用事件监听器,这些监听器用于在应用运行期间监听和响应各种事件。
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    
    // 尝试推断主应用类,这通常是包含main方法的类。这个类用于各种属性和注解的解析。
    this.mainApplicationClass = deduceMainApplicationClass();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  1. 初始化资源加载器:如果提供了ResourceLoader,它会被用于加载应用资源。
  2. 验证并存储主配置类:确保传入的主配置类不为null,然后将它们存储在一个LinkedHashSet中,保持添加顺序并去重。
  3. 推断Web应用类型:基于类路径中的类(如Servlet或Reactive相关类)推断应用是Servlet Web应用、Reactive Web应用还是非Web应用。
  4. 加载Bootstrappers:通过spring.factories文件加载早期启动阶段的Bootstrapper组件。
  5. 设置应用上下文初始化器和事件监听器:从spring.factories文件中加载ApplicationContextInitializer和ApplicationListener的实例,并设置它们。
  6. 推断主应用类:尝试找到包含main方法的类,通常是启动类,用于属性解析等。

总结

由此我们可以推断在Spring Boot中,BootstrapRegistryInitializer和ApplicationContextInitializer都可以通过spring.factories文件进行加载。

# BootstrapRegistryInitializer

感知特定阶段:感知 引导初始化,创建引导上下文 bootstrapContext 的时候触发。

BootstrapRegistryInitializer是Spring Boot 2.4中引入的一个新特性,它允许开发者在Spring Boot应用的引导阶段进行自定义初始化操作。这个引导阶段发生在Spring应用上下文被创建之前,因此它为早期配置提供了一个扩展点。

要使用BootstrapRegistryInitializer,需要创建一个实现了该接口的类,并重写initialize方法:

public class MyBootstrapRegistryInitializer implements BootstrapRegistryInitializer {
    @Override
    public void initialize(BootstrapRegistry registry) {
        // 在这里执行自定义的引导初始化逻辑
        System.out.println("BootstrapRegistryInitializer===引导类初始化");
    }
}
1
2
3
4
5
6
7

image-20240331022758969

注册BootstrapRegistryInitializer

有两种主要方式可以将MyBootstrapRegistryInitializer注册到Spring Boot应用中,让它在引导阶段被调用:

1. 通过spring.factories文件注册:将BootstrapRegistryInitializer实现类配置在resources/META-INF/spring.factories文件中:

org.springframework.boot.BootstrapRegistryInitializer=com.example.MyBootstrapRegistryInitializer
1

这样,Spring Boot启动时会自动发现并调用MyBootstrapRegistryInitializer。

2. 编程方式注册:在Spring Boot应用的主类中,通过SpringApplication实例的addBootstrapRegistryInitializer方法添加:

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(MainApplication.class);
        springApplication.addBootstrapRegistryInitializer(new MyBootstrapRegistryInitializer());
        springApplication.run(args);
    }
}
1
2
3
4
5
6
7
8

这种方式允许更灵活的控制初始化时机和条件。

应用场景

BootstrapRegistryInitializer特别适用于那些需要在Spring Boot应用的早期阶段执行的操作,例如:

  • 密钥校对和授权:在应用正式启动前,进行必要的安全性检查,如密钥验证或软件授权。
  • 早期资源准备:比如提前准备或配置必须在应用上下文创建之前就需要的资源。
  • 环境检测和配置:在Spring环境准备之前,根据不同的环境进行特定的配置或者调整。

# ApplicationContextInitializer

感知特定阶段:感知 IOC 容器初始化。

ApplicationContextInitializer是一个Spring框架的接口,用于在Spring应用上下文(ApplicationContext)刷新之前执行自定义的初始化代码。这提供了一个钩子(hook),允许开发者在Spring Boot应用启动过程中的早期介入,进行额外的配置或者检查。

要使用ApplicationContextInitializer,首先需要创建一个实现了该接口的类,并重写initialize方法:

public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        // 在这里执行自定义的初始化逻辑
        System.out.println("ApplicationContextInitialize===ApplicationContext刷新之前执行自定义的初始化");
    }
}
1
2
3
4
5
6
7

image-20240331023403305

注册ApplicationContextInitializer

有两种主要方式可以将MyApplicationContextInitializer注册到Spring Boot应用中,让它在应用上下文初始化阶段被调用:

1. 通过spring.factories文件注册:将ApplicationContextInitializer实现类配置在resources/META-INF/spring.factories文件中:

org.springframework.context.ApplicationContextInitializer=com.example.MyApplicationContextInitializer
1

这样,Spring Boot启动时会自动发现并调用MyApplicationContextInitializer。

2. 编程方式注册:在Spring Boot应用的主类中,通过SpringApplication实例的addInitializers方法添加:

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(MainApplication.class);
        springApplication.addInitializers(new MyApplicationContextInitializer());
        springApplication.run(args);
    }
}
1
2
3
4
5
6
7
8

这种方式允许更灵活地控制初始化器的添加和配置。

应用场景

ApplicationContextInitializer特别适用于需要在Spring Boot应用的上下文初始化阶段执行的任务,例如:

  • 环境定制:在上下文刷新之前修改应用的环境或配置。
  • 条件配置:根据特定条件动态地调整Spring配置。
  • 资源准备:提前准备或配置那些必须在应用上下文创建和Bean初始化之前就需要的资源。

通过实现和注册ApplicationContextInitializer,开发者可以在Spring Boot应用的上下文初始化阶段插入自定义逻辑,这为Spring应用的配置和启动提供了更多的灵活性和控制能力。与BootstrapRegistryInitializer相比,ApplicationContextInitializer更关注于应用上下文的准备和配置,为Bean的加载和初始化提供前期的定制和配置。

# ApplicationListener

感知全阶段:基于事件机制,感知事件。一旦到了哪个阶段可以做别的事。

ApplicationListener是Spring框架的一个核心接口,用于监听和响应Spring应用中发生的各种事件。在Spring Boot应用中,事件机制允许开发者在应用的生命周期中的特定时刻执行自定义逻辑或处理。

要使用ApplicationListener,首先需要创建一个实现了该接口的类。可以选择监听所有事件,也可以专门监听特定类型的事件:

  • 监听所有事件:

    public class MyApplicationListener1 implements ApplicationListener<ApplicationEvent> {
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            System.out.println("事件都触发" + event.getSource());
        }
    }
    
    1
    2
    3
    4
    5
    6

    image-20240331023558531

  • 专门监听特定事件:

    如果只对某种类型的事件感兴趣,可以在实现ApplicationListener时指定感兴趣的事件类型:

    public class MyApplicationListener2 implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
        @Override
        public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
            System.out.println("环境准备事件触发" + event.getSource());
        }
    }
    
    1
    2
    3
    4
    5
    6

注册ApplicationListener

与其他Spring Boot扩展点类似,ApplicationListener也可以通过以下两种方式进行注册以使其生效:

1. 通过spring.factories文件注册:将ApplicationListener实现类配置在resources/META-INF/spring.factories文件中:

org.springframework.context.ApplicationListener=com.example.MyApplicationListener1
1

这样,Spring Boot启动时会自动发现并调用该ApplicationListener。

2. 编程方式注册:在Spring Boot应用的主类中,通过SpringApplication实例的addListeners方法添加:

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(MainApplication.class);
        springApplication.addListeners(new MyApplicationListener1());
        springApplication.run(args);
    }
}
1
2
3
4
5
6
7
8

这种方式允许在应用启动时动态添加监听器。

应用场景

ApplicationListener非常适用于在Spring Boot应用的不同生命周期阶段执行特定的逻辑,例如:

  • 在应用启动前后进行日志记录。
  • 在应用上下文刷新前后执行自定义初始化或清理操作。
  • 在应用环境准备好后进行自定义配置检查或修改。

# SpringApplicationRunListener

感知全阶段生命周期 + 各种阶段都能自定义操作,功能更完善。

SpringApplicationRunListener是一个接口,用于在Spring Boot启动过程的不同阶段执行自定义逻辑。它可以监听到从应用启动开始直到运行完毕的整个生命周期。

要使用SpringApplicationRunListener,你需要创建一个实现了该接口的类,并实现其方法。例如,starting、environmentPrepared、contextPrepared、contextLoaded、started、running、failed等,每个方法对应于应用生命周期的不同阶段。

注册方法:SpringApplicationRunListener通过在resources/META-INF/spring.factories文件中进行配置来注册,例如:

org.springframework.boot.SpringApplicationRunListener=com.example.MySpringApplicationRunListener
1

这个方法上面已经有详细讲过了,这里不过多叙述。

# ApplicationRunner

感知特定阶段:感知应用就绪 Ready。卡死应用,就不会就绪。

ApplicationRunner接口提供了一个run方法,该方法接受一个ApplicationArguments参数,它封装了传递给main方法的应用程序参数。通过使用ApplicationRunner,你可以访问应用程序参数的处理后版本,这样可以更方便地处理命令行参数。

要使用ApplicationRunner,你需要:

  1. 创建一个实现了ApplicationRunner接口的类。
  2. 重写run方法,并在其中实现你的自定义逻辑。
  3. 将该类注册为Spring组件(使用@Component注解)。
@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner: 应用就绪,执行自定义逻辑");
    }
}
1
2
3
4
5
6
7

image-20240330160219026

通过将实现了ApplicationRunner的类注解为@Component,Spring Boot会自动检测到它,并在应用准备就绪时执行其run方法。你不需要手动注册这个Runner。

# CommandLineRunner

感知特定阶段:感知应用就绪 Ready。卡死应用,就不会就绪。

CommandLineRunner是Spring Boot提供的一个简单接口,用于在Spring应用上下文完全初始化和刷新之后、应用准备好接受请求之前执行代码。这个接口特别适用于执行初始化逻辑,如数据库迁移、数据加载、或启动某些后台任务等。

要使用CommandLineRunner,你需要:

  1. 创建一个实现了CommandLineRunner接口的类。
  2. 重写run方法,并在其中实现你的自定义逻辑。
  3. 将该类注册为Spring组件(使用@Component注解)。
@Component
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner: 应用就绪,执行自定义逻辑");
        // 可以通过args参数访问命令行提供的参数
        for (String arg : args) {
            System.out.println(arg);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11

image-20240330160251712

通过将实现了CommandLineRunner的类注解为@Component,Spring Boot会自动检测到它,并在应用准备就绪时执行其run方法。你不需要手动注册这个Runner。

ApplicationRunner和CommandLineRunner都是Spring Boot提供的两个用于在应用启动并准备好接受请求之后执行特定代码的接口。它们都是在Spring Boot的应用上下文完全初始化、刷新并准备好接受请求的最后阶段被调用,但在发布ApplicationReadyEvent事件之前执行。这为开发者在应用启动过程的最后阶段执行初始化代码或启动任务提供了便捷的方法。

ApplicationRunner和CommandLineRunner特别适用于以下场景:

  • 执行初始化逻辑:在应用启动并准备好之后执行一些初始化逻辑,例如加载数据到缓存、检查外部系统的可用性等。
  • 命令行工具:如果你的Spring Boot应用需要作为一个命令行工具使用,ApplicationRunner提供了一个便捷的方法来处理命令行参数,并执行相关逻辑。
  • 后台任务启动:启动后台处理任务,如开始定时任务、启动异步服务等。

# 最佳实战

  • 如果项目启动前做事: BootstrapRegistryInitializer 和 ApplicationContextInitializer
  • 如果想要在项目启动完成后做事:ApplicationRunner 和 CommandLineRunner
  • 如果要干涉生命周期做事:SpringApplicationRunListener
  • 如果想要用事件机制:ApplicationListener

# 5. 事件完整触发流程

Spring Boot的事件机制提供了一种在应用生命周期的关键点插入自定义逻辑的方式。

事件监听器允许你将事件的发布和事件的响应逻辑解耦。事件源不需要知道谁将响应它的事件,而监听器也不需要知道事件是由谁发布的。这种解耦使得你的应用组件更加独立,易于测试和维护。

# 1. ApplicationStartingEvent

  • 触发时机:应用启动时,任何处理之前,此时除了注册Listeners和Initializers外,未进行其他操作。
  • 用途:可以用来做一些最早期的准备工作,比如日志的特别处理。
public class CustomApplicationStartingEventListener implements ApplicationListener<ApplicationStartingEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartingEvent event) {
        // 这里可以进行一些最早期的准备工作
        System.out.println("Application is starting");
    }
}
1
2
3
4
5
6
7

# 2. ApplicationEnvironmentPreparedEvent

  • 触发时机:Environment已经准备好,但上下文Context未创建。
  • 用途:可以用来对配置环境做进一步处理或检查。
public class CustomEnvironmentPreparedEventListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        // 这里可以对配置环境做一些处理
        System.out.println("Environment is prepared");
    }
}
1
2
3
4
5
6
7

# 3. ApplicationContextInitializedEvent

  • 触发时机:ApplicationContext准备好,ApplicationContextInitializers被调用,但任何Bean未加载。
  • 用途:可以用来对ApplicationContext做一些自定义的初始化操作。
  • 代码示例:
public class CustomContextInitializedEventListener implements ApplicationListener<ApplicationContextInitializedEvent> {
    @Override
    public void onApplicationEvent(ApplicationContextInitializedEvent event) {
        // 自定义的 ApplicationContext 初始化操作
        System.out.println("ApplicationContext is initialized");
    }
}
1
2
3
4
5
6
7

# 4. ApplicationPreparedEvent

  • 触发时机:容器刷新之前,Bean定义信息已加载,但尚未实例化Bean。
  • 用途:适合做一些Bean定义的预处理工作。
public class CustomPreparedEventListener implements ApplicationListener<ApplicationPreparedEvent> {
    @Override
    public void onApplicationEvent(ApplicationPreparedEvent event) {
        // Bean定义的预处理
        System.out.println("Application is prepared, before refresh");
    }
}
1
2
3
4
5
6
7

# 5. ApplicationStartedEvent

  • 触发时机:容器刷新完成,但在任何ApplicationRunner或CommandLineRunner被调用之前。
  • 用途:可以用来执行一些在容器完全启动后需要立即执行的操作。
public class CustomStartedEventListener implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        // 容器刷新完成的操作
        System.out.println("Application is started");
    }
}
1
2
3
4
5
6
7

# 6. AvailabilityChangeEvent(LivenessState.CORRECT)

  • 触发时机:应用存活探针,用于感知应用是否存活。
  • 用途:检测应用是否处于可运行状态。
  • 代码示例:通常是框架级别的事件,不需要手动触发。

# 7. ApplicationReadyEvent

  • 触发时机:任何ApplicationRunner或CommandLineRunner被调用后。
  • 用途:标识应用已经准备好接收请求。
public class CustomReadyEventListener implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // 应用准备好接收请求
        System.out.println("Application is ready to receive requests");
    }
}
1
2
3
4
5
6
7

# 8. AvailabilityChangeEvent(ReadinessState.ACCEPTING_TRAFFIC)

  • 触发时机:就绪探针,代表此时可以接受请求。

  • 用途:表示应用已准备好接受外部流量。

  • 如何使用:通常是框架级别的事件,不需要手动触发。

# 9. ApplicationFailedEvent

  • 触发时机:启动过程中出现异常。
  • 用途:可以用来处理启动失败的情况,比如记录错误日志,发送警报等。
public class CustomFailedEventListener implements ApplicationListener<ApplicationFailedEvent> {
    @Override
    public void onApplicationEvent(ApplicationFailedEvent event) {
        // 处理启动失败的情况
        System.out.println("Application failed to start");
    }
}
1
2
3
4
5
6
7

这些监听器可以在Spring Boot应用的main方法中通过SpringApplication.addListeners方法添加,或者通过在Spring Boot应用中将它们声明为@Component来自动注册。这提供了一种强大的机制,允许在应用生命周期的不同阶段插入自定义行为。

# 10. 如何确保监听器执行顺序

Spring事件监听机制允许多个监听器监听同一个事件,而这些监听器的执行顺序通常是不确定的。默认情况下,Spring并不保证监听器的执行顺序。这意味着,如果应用的逻辑依赖于监听器的执行顺序,那么你需要采取措施来确保这一点。

1. 使用@Order注解:Spring允许通过@Order注解或实现Ordered接口来指定组件的加载顺序。你可以在监听器类上使用这个注解来定义执行顺序。数值越小,优先级越高,越早执行。

@Component
@Order(1)
public class FirstEventListener implements ApplicationListener<MyEvent> {
    // ...
}

@Component
@Order(2)
public class SecondEventListener implements ApplicationListener<MyEvent> {
    // ...
}
1
2
3
4
5
6
7
8
9
10
11

2. 实现Ordered接口:如果你希望动态地决定顺序,可以让监听器实现Ordered接口,并在getOrder()方法中返回顺序值。

public class MyEventListener implements ApplicationListener<MyEvent>, Ordered {
    @Override
    public int getOrder() {
        // 定义顺序逻辑
        return 1;
    }

    @Override
    public void onApplicationEvent(MyEvent event) {
        // ...
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

动态地决定顺序是指你可以根据应用的当前状态或配置来决定监听器执行的顺序,而不是事先固定写死在注解或代码中。这种方式通过实现Ordered接口实现,使得你可以在运行时计算出监听器的执行顺序。

举一个简单的例子,假设你有两个监听器,它们都监听相同的事件,但你希望根据某些条件(如配置文件中的设置或应用的某个状态)来决定哪个监听器先执行。

下面是如何实现动态顺序的一个示例:

import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Component
public class DynamicOrderEventListener implements ApplicationListener<MyEvent>, Ordered {

    // 这个方法返回监听器的顺序
    @Override
    public int getOrder() {
        // 假设这个顺序是根据某个动态条件决定的
        // 例如,从配置文件加载,或者根据某些业务逻辑计算得出
        boolean someCondition = checkSomeCondition();
        return someCondition ? 1 : 2; // 如果条件满足,这个监听器先执行;否则,后执行
    }

    @Override
    public void onApplicationEvent(MyEvent event) {
        // 处理事件的逻辑
    }

    private boolean checkSomeCondition() {
        // 这里实现检查条件的逻辑
        // 例如,查询数据库,检查配置文件,或者其他任何可以决定执行顺序的逻辑
        return true; // 假设条件检查的结果
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

在上面的例子中,getOrder方法返回的顺序值是根据checkSomeCondition方法的结果动态决定的。这意味着你可以根据应用的当前状态或环境条件来改变监听器的执行顺序。这种方式提供了更高的灵活性,允许你根据实际需求调整监听器的执行策略。

# 11. 使用事件监听的优势

  1. 模块化:你可以将不同的功能或业务逻辑封装在不同的监听器中,每个监听器处理特定的事件。这种模块化的结构使得代码更加清晰,也更容易维护和理解。
  2. 低耦合:监听器对于它们监听的事件之间是松散耦合的。这意味着你可以自由地添加、修改或移除监听器,而不会对其他部分的代码产生影响,从而降低了整体代码的耦合度。
  3. 易于扩展:当需要引入新的行为或逻辑时,你只需添加新的事件监听器而无需修改现有的生命周期管理代码。这使得在应用成长和演进过程中加入新功能变得更加容易。
  4. 清晰的职责分离:通过定义专门的监听器来响应特定的事件,你可以更清晰地分离出系统的不同职责和功能区,每个监听器只关注它需要处理的那部分逻辑。

相比之下,在生命周期方法中直接编写代码

  • 如果你在SpringBoot生命周期的回调方法中直接添加代码,随着逻辑的增加,这个方法可能会变得非常庞大和复杂。这不仅会增加理解和维护的难度,还会增加代码间的耦合性。
  • 直接在生命周期方法中添加代码,虽然简单直接,但随着应用逻辑的增长,可能会导致代码结构混乱,特别是当处理多个不同的任务时,代码的可读性和可维护性会受到影响。

上面 9 大事件是 Spring Boot 内置的。在项目启动后,我们也可以自定义事件使用,具体看 Spring Boot - 事件驱动 的使用方式。

编辑此页 (opens new window)
上次更新: 2025/03/21, 11:11:36
Spring Boot - 自定义SpringApplication
Spring Boot - 事件驱动

← Spring Boot - 自定义SpringApplication Spring Boot - 事件驱动→

Theme by Vdoing | Copyright © 2019-2025 程序员scholar
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式