SpringBoot事件和监听器

发布时间 2023-10-30 18:54:36作者: 谁风霜依旧

事件和监听器

生命周期监听

场景:监听应用的生命周期

监听器-SpringApplicationRunListener

  1. 自定义SpringApplicationRunListener来监听事件;
    1.1. 编写SpringApplicationRunListener 实现类
    1.2. 在 META-INF/spring.factories 中配置 org.springframework.boot.SpringApplicationRunListener=自己的Listener,还可以指定一个有参构造器,接受两个参数(SpringApplication application, String[] args)
    1.3. springboot 在spring-boot.jar中配置了默认的 Listener,如下

org\springframework\boot\spring-boot\3.1.5\spring-boot-3.1.5.jar!\META-INF\spring.factories

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
/**
 * Listener先要从 META-INF/spring.factories 读到
 *
 * 1、引导: 利用 BootstrapContext 引导整个项目启动
 *      starting:              应用开始,SpringApplication的run方法一调用,只要有了 BootstrapContext 就执行
 *      environmentPrepared:   环境准备好(把启动参数等绑定到环境变量中),但是ioc还没有创建;【调一次】
 * 2、启动:
 *      contextPrepared:       ioc容器创建并准备好,但是sources(主配置类)没加载。并关闭引导上下文;组件都没创建  【调一次】
 *      contextLoaded:         ioc容器加载。主配置类加载进去了。但是ioc容器还没刷新(我们的bean没创建)。
 *      =======截止以前,ioc容器里面还没造bean呢=======
 *      started:               ioc容器刷新了(所有bean造好了),但是 runner 没调用。
 *      ready:                  ioc容器刷新了(所有bean造好了),所有 runner 调用完了。
 * 3、运行
 *     以前步骤都正确执行,代表容器running。
 */

生命周期全流程

事件触发时机

各种回调监听器

  • BootstrapRegistryInitializer: 感知特定阶段:感知引导初始化
    • META-INF/spring.factories
    • 创建引导上下文bootstrapContext的时候触发。
    • application.addBootstrapRegistryInitializer();
    • 场景:进行密钥校对授权。
  • ApplicationContextInitializer: 感知特定阶段: 感知ioc容器初始化
    • META-INF/spring.factories
    • application.addInitializers();
  • ApplicationListener: 感知全阶段:基于事件机制,感知事件。 一旦到了哪个阶段可以做别的事
    • @Bean或@EventListener: 事件驱动
    • SpringApplication.addListeners(…)SpringApplicationBuilder.listeners(…)
    • META-INF/spring.factories
  • SpringApplicationRunListener: 感知全阶段生命周期 + 各种阶段都能自定义操作; 功能更完善。
    • META-INF/spring.factories
  • ApplicationRunner: 感知特定阶段:感知应用就绪Ready。卡死应用,就不会就绪
    • @Bean
  • CommandLineRunner: 感知特定阶段:感知应用就绪Ready。卡死应用,就不会就绪
    • @Bean

最佳实战:

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

完整触发流程

9大事件触发顺序&时机

  1. ApplicationStartingEvent:应用启动但未做任何事情, 除过注册listeners and initializers.
  2. ApplicationEnvironmentPreparedEvent: Environment 准备好,但context 未创建.
  3. ApplicationContextInitializedEvent: ApplicationContext 准备好,ApplicationContextInitializers 调用,但是任何bean未加载
  4. ApplicationPreparedEvent: 容器刷新之前,bean定义信息加载
  5. ApplicationStartedEvent: 容器刷新完成, runner未调用
    ---以下就开始插入了探针机制---
  6. AvailabilityChangeEventLivenessState.CORRECT应用存活; 存活探针
  7. ApplicationReadyEvent: 任何runner被调用
  8. AvailabilityChangeEventReadinessState.ACCEPTING_TRAFFIC就绪探针,可以接请求
  9. ApplicationFailedEvent :启动出错

应用事件发送顺序如下:

感知应用是否存活了:可能植物状态,虽然活着但是不能处理请求。
应用是否就绪了:能响应请求,说明确实活的比较好。

代码使用SpringApplicationRunListenerApplicationListener

/resource/META-INF/spring.factories

org.springframework.boot.SpringApplicationRunListener=\
com.atguigu.boot3.core.listener.MyApplicationListener

org.springframework.context.ApplicationListener=\
com.atguigu.boot3.core.listener.MyEventListener

MyApplicationListener.java

用来感知SpringBoot生命周期

package com.atguigu.boot3.core.listener;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

import java.time.Duration;

/**
 * SpringBoot的生命周期
 */
@Slf4j
public class MyApplicationListener implements SpringApplicationRunListener {

    /**
     * 在run方法首次启动时立即调用。可以用于非常早期的初始化。
     */
    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println("***starting***");
    }

    /**
     * 环境准备好就会调用,但是在 ApplicationContext 创建前
     */
    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
        System.out.println("***environmentPrepared***");
    }

    /**
     * ApplicationContext已经被创建,但是还未加载
     */
    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("***contextPrepared***");
    }

    /**
     * 在ApplicationContext加载后但在刷新之前调用
     */
    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("***contextLoaded***");
    }

    /**
     * ApplicationContext已经刷新,但是CommandLineRunners和ApplicationRunners还未被调用
     */
    @Override
    public void started(ConfigurableApplicationContext context, Duration timeTaken) {
        System.out.println("***started***");
    }

    /**
     * 在调用了CommandLineRunners和ApplicationRunners后,ApplicationContext容器run方法运行之前运行
     */
    @Override
    public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
        System.out.println("***ready***");
    }

    /**
     * 在应用程序运行出错时运行
     */
    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("***failed***");
    }
}

MyEventListener.java
监听事件

package com.atguigu.boot3.core.listener;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

public class MyEventListener implements ApplicationListener<ApplicationEvent> {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("--[event]--" + event.getClass().getName());
    }
}

运行结果

E:\Java\jdk-17.0.5\bin\java.exe ...

--[event]--org.springframework.boot.context.event.ApplicationStartingEvent
***starting***
--[event]--org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
***environmentPrepared***

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.5)

--[event]--org.springframework.boot.context.event.ApplicationContextInitializedEvent
***contextPrepared***
2023-10-30T18:08:56.239+08:00  INFO 16448 --- [           main] com.atguigu.boot3.core.MainApplication   : Starting MainApplication using Java 17.0.5 with PID 16448 (E:\code\IdeaProjects\spring-boot-3\boot3-07-core\target\classes started by 朱俊伟 in E:\code\IdeaProjects\spring-boot-3)
2023-10-30T18:08:56.242+08:00  INFO 16448 --- [           main] com.atguigu.boot3.core.MainApplication   : The following 1 profile is active: "dev"
--[event]--org.springframework.boot.context.event.ApplicationPreparedEvent
***contextLoaded***
2023-10-30T18:08:57.269+08:00  INFO 16448 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-10-30T18:08:57.281+08:00  INFO 16448 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-10-30T18:08:57.281+08:00  INFO 16448 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.15]
2023-10-30T18:08:57.367+08:00  INFO 16448 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-10-30T18:08:57.368+08:00  INFO 16448 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1064 ms
2023-10-30T18:08:57.765+08:00  INFO 16448 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
--[event]--org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent
--[event]--org.springframework.context.event.ContextRefreshedEvent
2023-10-30T18:08:57.774+08:00  INFO 16448 --- [           main] com.atguigu.boot3.core.MainApplication   : Started MainApplication in 2.248 seconds (process running for 2.97)
--[event]--org.springframework.boot.context.event.ApplicationStartedEvent
--[event]--org.springframework.boot.availability.AvailabilityChangeEvent
***started***
--[event]--org.springframework.boot.context.event.ApplicationReadyEvent
--[event]--org.springframework.boot.availability.AvailabilityChangeEvent
***ready***
--[event]--org.springframework.boot.availability.AvailabilityChangeEvent
--[event]--org.springframework.context.event.ContextClosedEvent

参考:
https://www.yuque.com/leifengyang/springboot3/lliphvul8b19pqxp#beI2B