目录

Spring Boot - 生命周期与事件

原理会涉及源码,建议看视频学习会更容易理解,本内容不涉及源码解析,内容都学习自视频。

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

# 前言

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

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

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

# 生命周期监听

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

public class MyListener implements SpringApplicationRunListener {

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

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

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

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

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

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

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.out.println("项目启动失败");
        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

Spring Boot 初始化会经过三大步骤:

引导:利用 BootstrapContext 引导整个项目启动

  • starting:应用开始,SpringApplication 的 run 方法一调用,只要有了 BootstrapContext 就执行
  • environmentPrepared:环境准备好(把启动参数等绑定到环境变量中),但是 ioc 还没有创建【调一次】

启动项目

  • contextPrepared:ioc 容器创建并准备好,但是 sources(主配置类)没加载。并关闭引导上下文;组件都没创建【调一次】

  • contextLoaded:ioc 容器加载。主配置类加载进去了。但是 ioc 容器还没刷新(Bean 没创建)

    截止以前,ioc 容器里面还没造 Bean

  • started:ioc 容器刷新了(所有bean造好了),但是 runner 没调用

  • ready:ioc 容器刷新了(所有bean造好了),所有 runner 调用完了

运行项目

以前步骤都正确执行,代表容器 Running。

# 事件触发时机

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

# BootstrapRegistryInitializer

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

public class MyBootstrapRegistryInitializer implements BootstrapRegistryInitializer {
    @Override
    public void initialize(BootstrapRegistry registry) {
        System.out.println("引导类初始化");
    }
}
1
2
3
4
5
6

当时这样子还是没办法被 Spring Boot 使用,需要告诉将该类放到 Spring 容器里(现在处于初始化,所以不能使用 @Component,这些注解是在初始化后才生效)。

配置在 resource/META-INF/spring.factories 文件下,这样 Spring Boot 就会扫描该文件的内容,放到容器中

org.springframework.boot.BootstrapRegistryInitializer=cn.youngkbt.boot.lift.MyBootstrapRegistryInitializer
1

通过主程序 application 的 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

场景:进行密钥校对授权。

# ApplicationContextInitializer

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

这些事件的使用和 BootstrapRegistryInitializer 都一样。

自定义类实现 ApplicationContextInitializer

public class MyApplicationContextInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        System.out.println("ApplicationContextInitializer");
    }
}
1
2
3
4
5
6

配置在 resource/META-INF/spring.factories 文件下,这样 Spring Boot 就会扫描该文件的内容,放到容器中

org.springframework.context.ApplicationContextInitializer=cn.youngkbt.boot.lift.MyApplicationContextInitializer
1

通过主程序 application 的 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

# ApplicationListener

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

Spring Boot 除了生命周期,还有事件机制,每个生命周期都有对应的事件,而 ApplicationListener 可以监听这些事件的触发时机。

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

这样每个事件触发的时候,都会执行 onApplicationEvent 方法。

如果我们想监听某个事件,如 ApplicationEnvironmentPreparedEvent 事件(Environment 准备好,但context 未创建),则:

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

因此 ApplicationListener 不加泛型则每个事件都触发 onApplicationEvent 方法,加了泛型,则只监听泛型里的事件类。

当然这也需要注册到 Spring 容器才能使用:

配置在 resource/META-INF/spring.factories 文件下,这样 Spring Boot 就会扫描该文件的内容,放到容器中

org.springframework.context.ApplicationListener=cn.youngkbt.boot.lift.MyApplicationListener
1

通过主程序 application 的 addListeners() 添加

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

# SpringApplicationRunListener

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

使用方法、注册方法和上面一样。

# ApplicationRunner

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

# CommandLineRunner

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

# 最佳实战

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

# 事件完整触发流程

9 大事件触发顺序 & 时机

  1. ApplicationStartingEvent:应用启动但未做任何事情, 除过注册 LKisteners 和 Initializers.
  2. ApplicationEnvironmentPreparedEvent:Environment 准备好,但 Context 未创建
  3. ApplicationContextInitializedEvent: ApplicationContext 准备好,ApplicationContextInitializers 调用,但是任何 Bean 未加载
  4. ApplicationPreparedEvent: 容器刷新之前,Bean 定义信息加载
  5. ApplicationStartedEvent: 容器刷新完成,Runner 未调用

以下就开始插入了 探针机制,搭配云原生使用:

  1. AvailabilityChangeEventLivenessState.CORRECT 应用存活,这是 存活探针
  2. ApplicationReadyEvent: 任何 Runner 被调用
  3. AvailabilityChangeEventReadinessState.ACCEPTING_TRAFFIC就绪探针,代表此时可以接请求
  4. ApplicationFailedEvent:启动出错

存活探针:感知应用是否 存活了,可能植物状态,虽然活着但是不能处理请求。

就绪探针:应用是否 就绪 了,能响应请求,说明确实活的比较好。

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

更新时间: 2024/01/17, 05:48:13
最近更新
01
JVM调优
12-10
02
jenkins
12-10
03
Arthas
12-10
更多文章>