1 温故而知新

温故而知新,我们来简单回顾一下上篇的内容,上一篇我们分析了SpringBoot 启动时广播生命周期事件的原理,现将关键步骤再浓缩总结下:

  1. 为广播 SpringBoot 内置生命周期事件做前期准备:1)首先加载ApplicationListener监听器实现类;2)其次加载 SPI 扩展类EventPublishingRunListener
  2. SpringBoot 启动时利用EventPublishingRunListener广播生命周期事件,然后ApplicationListener监听器实现类监听相应的生命周期事件执行一些初始化逻辑的工作。

2 引言

上篇文章的侧重点是分析了 SpringBoot 启动时广播生命周期事件的原理,此篇文章我们再来详细分析 SpringBoot 内置的 7 种生命周期事件的源码。

3 SpringBoot 生命周期事件源码分析

分析 SpringBoot 的生命周期事件,我们先来看一张类结构图:

10 SpringBoot内置生命周期事件详解 - 图1

由上图可以看到事件类之间的关系:

  1. 最顶级的父类是 JDK 的事件基类EventObject
  2. 然后 Spring 的事件基类ApplicationEvent继承了 JDK 的事件基类EventObject
  3. 其次 SpringBoot 的生命周期事件基类SpringApplicationEvent继承了 Spring 的事件基类ApplicationEvent
  4. 最后 SpringBoot 具体的 7 个生命周期事件类再继承了 SpringBoot 的生命周期事件基类SpringApplicationEvent

3.1 JDK 的事件基类 EventObject

EventObject类是 JDK 的事件基类,可以说是所有 Java 事件类的基本,即所有的 Java 事件类都直接或间接继承于该类,源码如下:

  1. // EventObject.java
  2. public class EventObject implements java.io.Serializable {
  3. private static final long serialVersionUID = 5516075349620653480L;
  4. /**
  5. * The object on which the Event initially occurred.
  6. */
  7. protected transient Object source;
  8. /**
  9. * Constructs a prototypical Event.
  10. *
  11. * @param source The object on which the Event initially occurred.
  12. * @exception IllegalArgumentException if source is null.
  13. */
  14. public EventObject(Object source) {
  15. if (source == null)
  16. throw new IllegalArgumentException("null source");
  17. this.source = source;
  18. }
  19. /**
  20. * The object on which the Event initially occurred.
  21. *
  22. * @return The object on which the Event initially occurred.
  23. */
  24. public Object getSource() {
  25. return source;
  26. }
  27. /**
  28. * Returns a String representation of this EventObject.
  29. *
  30. * @return A a String representation of this EventObject.
  31. */
  32. public String toString() {
  33. return getClass().getName() + "[source=" + source + "]";
  34. }
  35. }

可以看到EventObject类只有一个属性source,这个属性是用来记录最初事件是发生在哪个类,举个栗子,比如在 SpringBoot 启动过程中会发射ApplicationStartingEvent事件,而这个事件最初是在SpringApplication类中发射的,因此source就是SpringApplication对象。

3.2 Spring 的事件基类 ApplicationEvent

ApplicationEvent继承了 DK 的事件基类EventObject类,是 Spring 的事件基类,被所有 Spring 的具体事件类继承,源码如下:

  1. // ApplicationEvent.java
  2. /**
  3. * Class to be extended by all application events. Abstract as it
  4. * doesn't make sense for generic events to be published directly.
  5. *
  6. * @author Rod Johnson
  7. * @author Juergen Hoeller
  8. */
  9. public abstract class ApplicationEvent extends EventObject {
  10. /** use serialVersionUID from Spring 1.2 for interoperability. */
  11. private static final long serialVersionUID = 7099057708183571937L;
  12. /** System time when the event happened. */
  13. private final long timestamp;
  14. /**
  15. * Create a new ApplicationEvent.
  16. * @param source the object on which the event initially occurred (never {@code null})
  17. */
  18. public ApplicationEvent(Object source) {
  19. super(source);
  20. this.timestamp = System.currentTimeMillis();
  21. }
  22. /**
  23. * Return the system time in milliseconds when the event happened.
  24. */
  25. public final long getTimestamp() {
  26. return this.timestamp;
  27. }
  28. }

可以看到ApplicationEvent有且仅有一个属性timestamp,该属性是用来记录事件发生的时间。

3.3 SpringBoot 的事件基类 SpringApplicationEvent

SpringApplicationEvent类继承了 Spring 的事件基类ApplicationEvent,是所有 SpringBoot 内置生命周期事件的父类,源码如下:

  1. /**
  2. * Base class for {@link ApplicationEvent} related to a {@link SpringApplication}.
  3. *
  4. * @author Phillip Webb
  5. */
  6. @SuppressWarnings("serial")
  7. public abstract class SpringApplicationEvent extends ApplicationEvent {
  8. private final String[] args;
  9. public SpringApplicationEvent(SpringApplication application, String[] args) {
  10. super(application);
  11. this.args = args;
  12. }
  13. public SpringApplication getSpringApplication() {
  14. return (SpringApplication) getSource();
  15. }
  16. public final String[] getArgs() {
  17. return this.args;
  18. }
  19. }

可以看到SpringApplicationEvent有且仅有一个属性args,该属性就是 SpringBoot 启动时的命令行参数即标注@SpringBootApplication启动类中main函数的参数。

3.4 SpringBoot 具体的生命周期事件类

接下来我们再来看一下SpringBoot内置生命周期事件即SpringApplicationEvent的具体子类们。

3.4.1 ApplicationStartingEvent

  1. // ApplicationStartingEvent.java
  2. public class ApplicationStartingEvent extends SpringApplicationEvent {
  3. public ApplicationStartingEvent(SpringApplication application, String[] args) {
  4. super(application, args);
  5. }
  6. }

SpringBoot 开始启动时便会发布ApplicationStartingEvent事件,其发布时机在环境变量 Environment 或容器 ApplicationContext 创建前但在注册ApplicationListener具体监听器之后,标志标志SpringApplication开始启动。

3.4.2 ApplicationEnvironmentPreparedEvent

  1. // ApplicationEnvironmentPreparedEvent.java
  2. public class ApplicationEnvironmentPreparedEvent extends SpringApplicationEvent {
  3. private final ConfigurableEnvironment environment;
  4. /**
  5. * Create a new {@link ApplicationEnvironmentPreparedEvent} instance.
  6. * @param application the current application
  7. * @param args the arguments the application is running with
  8. * @param environment the environment that was just created
  9. */
  10. public ApplicationEnvironmentPreparedEvent(SpringApplication application,
  11. String[] args, ConfigurableEnvironment environment) {
  12. super(application, args);
  13. this.environment = environment;
  14. }
  15. /**
  16. * Return the environment.
  17. * @return the environment
  18. */
  19. public ConfigurableEnvironment getEnvironment() {
  20. return this.environment;
  21. }
  22. }

可以看到ApplicationEnvironmentPreparedEvent事件多了一个environment属性,我们不妨想一下,多了environment属性的作用是啥? 答案就是ApplicationEnvironmentPreparedEvent事件的environment属性作用是利用事件发布订阅机制,相应监听器们可以从ApplicationEnvironmentPreparedEvent事件中取出environment变量,然后我们可以为environment属性增加属性值或读出environment变量中的值。

举个栗子: ConfigFileApplicationListener监听器就是监听了ApplicationEnvironmentPreparedEvent事件,然后取出ApplicationEnvironmentPreparedEvent事件的environment属性,然后再为environment属性增加application.properties配置文件中的环境变量值。

当 SpringApplication 已经开始启动且环境变量Environment已经创建后,并且为环境变量Environment配置了命令行和Servlet等类型的环境变量后,此时会发布ApplicationEnvironmentPreparedEvent事件。

监听ApplicationEnvironmentPreparedEvent事件的第一个监听器是ConfigFileApplicationListener,因为是ConfigFileApplicationListener监听器还要为环境变量Environment增加application.properties配置文件中的环境变量;此后还有一些也是监听ApplicationEnvironmentPreparedEvent事件的其他监听器监听到此事件时,此时可以说环境变量Environment几乎已经完全准备好了。

思考: 监听同一事件的监听器们执行监听逻辑时是有顺序的,我们可以想一下这个排序逻辑是什么时候排序的?还有为什么要这样排序呢?

3.4.3 ApplicationContextInitializedEvent

  1. // ApplicationContextInitializedEvent.java
  2. public class ApplicationContextInitializedEvent extends SpringApplicationEvent {
  3. private final ConfigurableApplicationContext context;
  4. /**
  5. * Create a new {@link ApplicationContextInitializedEvent} instance.
  6. * @param application the current application
  7. * @param args the arguments the application is running with
  8. * @param context the context that has been initialized
  9. */
  10. public ApplicationContextInitializedEvent(SpringApplication application,
  11. String[] args, ConfigurableApplicationContext context) {
  12. super(application, args);
  13. this.context = context;
  14. }
  15. /**
  16. * Return the application context.
  17. * @return the context
  18. */
  19. public ConfigurableApplicationContext getApplicationContext() {
  20. return this.context;
  21. }
  22. }

可以看到ApplicationContextInitializedEvent事件多了个ConfigurableApplicationContext类型的context属性,context属性的作用同样是为了相应监听器可以拿到这个context属性执行一些逻辑,具体作用将在3.4.4详述。

ApplicationContextInitializedEvent事件在ApplicationContext容器创建后,且为ApplicationContext容器设置了environment变量和执行了ApplicationContextInitializers的初始化方法后但在 bean 定义加载前触发,标志 ApplicationContext 已经初始化完毕。

扩展: 可以看到ApplicationContextInitializedEvent是在为context容器配置environment变量后触发,此时ApplicationContextInitializedEvent等事件只要有context容器的话,那么其他需要environment环境变量的监听器只需要从context中取出environment变量即可,从而ApplicationContextInitializedEvent等事件没必要再配置environment属性。

3.4.4 ApplicationPreparedEvent

  1. // ApplicationPreparedEvent.java
  2. public class ApplicationPreparedEvent extends SpringApplicationEvent {
  3. private final ConfigurableApplicationContext context;
  4. /**
  5. * Create a new {@link ApplicationPreparedEvent} instance.
  6. * @param application the current application
  7. * @param args the arguments the application is running with
  8. * @param context the ApplicationContext about to be refreshed
  9. */
  10. public ApplicationPreparedEvent(SpringApplication application, String[] args,
  11. ConfigurableApplicationContext context) {
  12. super(application, args);
  13. this.context = context;
  14. }
  15. /**
  16. * Return the application context.
  17. * @return the context
  18. */
  19. public ConfigurableApplicationContext getApplicationContext() {
  20. return this.context;
  21. }
  22. }

同样可以看到ApplicationPreparedEvent事件多了个ConfigurableApplicationContext类型的context属性,多了context属性的作用是能让监听该事件的监听器们能拿到context属性,监听器拿到context属性一般有如下作用:

  1. 从事件中取出context属性,然后可以增加一些后置处理器,比如ConfigFileApplicationListener监听器监听到ApplicationPreparedEvent事件后,然后取出context变量,通过context变量增加了PropertySourceOrderingPostProcessor这个后置处理器;
  2. 通过context属性取出beanFactory容器,然后注册一些bean,比如LoggingApplicationListener监听器通过ApplicationPreparedEvent事件的context属性取出beanFactory容器,然后注册了springBootLoggingSystem这个单例bean
  3. 通过context属性取出Environment环境变量,然后就可以操作环境变量,比如PropertiesMigrationListener

ApplicationPreparedEvent事件在ApplicationContext容器已经完全准备好时但在容器刷新前触发,在这个阶段bean定义已经加载完毕还有environment已经准备好可以用了。

3.4.5 ApplicationStartedEvent

  1. // ApplicationStartedEvent.java
  2. public class ApplicationStartedEvent extends SpringApplicationEvent {
  3. private final ConfigurableApplicationContext context;
  4. /**
  5. * Create a new {@link ApplicationStartedEvent} instance.
  6. * @param application the current application
  7. * @param args the arguments the application is running with
  8. * @param context the context that was being created
  9. */
  10. public ApplicationStartedEvent(SpringApplication application, String[] args,
  11. ConfigurableApplicationContext context) {
  12. super(application, args);
  13. this.context = context;
  14. }
  15. /**
  16. * Return the application context.
  17. * @return the context
  18. */
  19. public ConfigurableApplicationContext getApplicationContext() {
  20. return this.context;
  21. }
  22. }

ApplicationStartedEvent事件将在容器刷新后但ApplicationRunnerCommandLineRunnerrun方法执行前触发,标志Spring容器已经刷新,此时容器已经准备完毕了。

扩展: 这里提到了ApplicationRunnerCommandLineRunner接口有啥作用呢?我们一般会在Spring容器刷新完毕后,此时可能有一些系统参数等静态数据需要加载,此时我们就可以实现了ApplicationRunnerCommandLineRunner接口来实现静态数据的加载。

3.4.6 ApplicationReadyEvent

  1. // ApplicationReadyEvent.java
  2. public class ApplicationReadyEvent extends SpringApplicationEvent {
  3. private final ConfigurableApplicationContext context;
  4. /**
  5. * Create a new {@link ApplicationReadyEvent} instance.
  6. * @param application the current application
  7. * @param args the arguments the application is running with
  8. * @param context the context that was being created
  9. */
  10. public ApplicationReadyEvent(SpringApplication application, String[] args,
  11. ConfigurableApplicationContext context) {
  12. super(application, args);
  13. this.context = context;
  14. }
  15. /**
  16. * Return the application context.
  17. * @return the context
  18. */
  19. public ConfigurableApplicationContext getApplicationContext() {
  20. return this.context;
  21. }
  22. }

ApplicationReadyEvent事件在调用完ApplicationRunnerCommandLineRunnerrun方法后触发,此时标志SpringApplication已经正在运行。

3.4.7 ApplicationFailedEvent

  1. // ApplicationFailedEvent.java
  2. public class ApplicationFailedEvent extends SpringApplicationEvent {
  3. private final ConfigurableApplicationContext context;
  4. private final Throwable exception;
  5. /**
  6. * Create a new {@link ApplicationFailedEvent} instance.
  7. * @param application the current application
  8. * @param args the arguments the application was running with
  9. * @param context the context that was being created (maybe null)
  10. * @param exception the exception that caused the error
  11. */
  12. public ApplicationFailedEvent(SpringApplication application, String[] args,
  13. ConfigurableApplicationContext context, Throwable exception) {
  14. super(application, args);
  15. this.context = context;
  16. this.exception = exception;
  17. }
  18. /**
  19. * Return the application context.
  20. * @return the context
  21. */
  22. public ConfigurableApplicationContext getApplicationContext() {
  23. return this.context;
  24. }
  25. /**
  26. * Return the exception that caused the failure.
  27. * @return the exception
  28. */
  29. public Throwable getException() {
  30. return this.exception;
  31. }
  32. }
  33. 复制代码

可以看到ApplicationFailedEvent事件除了多了一个context属性外,还多了一个Throwable类型的exception属性用来记录 SpringBoot 启动失败时的异常。

ApplicationFailedEvent事件在 SpringBoot 启动失败时触发,标志 SpringBoot 启动失败。

4 小结

此篇文章相对简单,对 SpringBoot 内置的 7 种生命周期事件进行了详细分析。我们还是引用上篇文章的一张图来回顾一下这些生命周期事件及其用途:

10 SpringBoot内置生命周期事件详解 - 图2