yokila
yokila
Published on 2023-12-02 / 21 Visits
0
0

SpringBoot启动运行自定义任务及时机原理

零、本文说明

本文从“期望在Spring Boot启动时运行自定义任务”这一需求出发,简单说明了探究Application启动过程的方式和其中的重要节点。在介绍几种实现方式的同时,附带说明执行时机和原理参考。

值得注意:虽然本文基于Spring Boot,但是探究启动过程时,不可避免要结合Spring的内容。

本文探究基于:IDEA 2022.2.5社区版 + Spring Boot 2.6.0 (对应的Spring 5.3.13

一、Spring Boot的启动流程

了解Spring Boot的启动流程,主要目的便是可以知道自定义的启动任务应该放在哪个环节去执行,从而更好地满足各种各样奇奇怪怪的需求。

(如果你已经很熟悉这个流程了,则可以跳过,直接看自定义启动运行任务的几种方式)

而若是要想知道Spring Boot启动流程的细节,可以通过以下途径:

1、探索run方法

进入项目启动类,再进入到SpringApplication.run()方法的实现

启动类

不断地找run()方法的实际实现方法,可以找到下文这个Run方法。这便是应用启动时,会依次运行的内容。

SpringApplication.class

值得注意的是,这短短的几十行代码,里面涉及的内容实在是太庞大了,无论是从业者还是为了学习,都应该去深入探究一番,不仅仅是可以学到启动流程,还能学到很多Spring相关内部原理性的很多东西。

2、应用程序启动追踪

Spring允许使用ApplicationStartup来追踪应用启动过程(所以Spring Boot能追踪)

如下图所示,自定义SpringApplication方式,设置BufferingApplicationStartup缓存启动过程

启动类

Pom.xml引入actuator依赖包

actuator文档:https://docs.spring.io/spring-boot/docs/3.2.0-SNAPSHOT/actuator-api/htmlsingle/#overview

<!-- Spring Boot Actuator 模块提供了生产级别的功能,比如健康检查,审计,指标收集,HTTP 跟踪等,帮助我们监控和管理Spring Boot 应用。 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

application.yml中新增actuator配置(目的就是提供获取启动追踪的缓存的路径)

# actuator 配置
management:
  server:
    port: 12313 # 使用独立端口,不设置的话和web端口就一样
  endpoints:
    web:
      exposure:
        include: startup # 暴露 startup 端点

启动项目后,用get请求地址(不需要附带入参):localhost:12313/actuator/startup

Postman中请求示例

将会获得一个很长的JSON格式的启动追踪记录,可以保存成文本文件慢慢阅读。

UP主稍微阅读了一下,这个记录内容显示的启动流程大概如下(详细的自己找个项目去阅读完整版):

  1. spring.boot.application.starting

  2. spring.boot.application.environment-prepared

  3. spring.boot.application.context-prepared

  4. spring.boot.application.context-loaded

  5. spring.beans.instantiate

  6. spring.context.config-classes.parse(用ConfigurationClassPostProcessor 进行配置类的解析阶段)

  7. spring.context.beandef-registry.post-process(调用BeanDefinitionRegistryPostProcessor  bean)

  8. spring.context.bean-factory.post-process

  9. spring.beans.instantiate

  10. spring.context.config-classes.enhance

  11. spring.beans.instantiate(interface javax.servlet.ServletContextListener )

  12. spring.boot.webserver.create(TomcatServletWebServerFactory)

  13. spring.beans.instantiate(Application)

  14. spring.beans.instantiate(按照依赖关系,先后实例化各种自实现的bean)(此处涉及Spring框架对反转控制(IoC)原则的实现。IoC也被称为依赖注入(DI))

  15. spring.beans.smart-initialize(SmartInitializingSingleton Bean的初始化)

  16. spring.context.refresh

  17. spring.boot.application.started

  18. spring.boot.application.read

3、官方文档参考

Spring官方文档:https://docs.spring.io/spring-framework/reference/core/appendix/application-startup-steps.html#page-title

Spring官方文档(中文网):https://springdoc.cn/spring/core.html#core.appendix.application-startup-steps

SpringBoot官方文档描述Application event发布顺序:

https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.spring-application.application-events-and-listeners

SpringBoot官方文档描述Application event发布顺序(中文网):https://springdoc.cn/spring-boot/features.html#features.spring-application.application-events-and-listeners

二、Application启动运行自定义任务的方式列举

下面两种方式在Spring Boot Application中的执行时机:Application已启动(applicationStartedEvent),应用程序的Liveness为CORRECT状态。

注意,下面两种方式的方法都执行完以后,应用程序的ReadinessState才会变成ACCEPTING_TRAFFIC状态。此时应用才算准备好了,可以开始处理外部来的请求。

特别说明:因为下面两种方式执行的时候,Application都启动好了,所以可以任意使用@Autowired等方式注入bean依赖项来用,无需顾虑。

1、实现ApplicationRunner接口

@Component
public class StartRunApplicationRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) {
        System.out.println("--------------------开始运行StartRunApplicationRunner-------------------");
        for (String optionName : args.getOptionNames()) {
            System.out.println("optionName: " + optionName + ", optionValue: " + args.getOptionValues(optionName));
        }
        System.out.println("--------------------结束运行StartRunApplicationRunner-------------------");
    }
}

启动后,若有传入启动参数则可以读取到。

注意,启动参数需要这样写(多个参数用空格隔开,每个参数的参数名需要以两个减号开头,以等号的形式设置参数值):--a=testArg1 --b=Arg2

示例

2、实现CommandLineRunner接口

@Component
public class StartRunCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("--------------------开始运行StartRunCommandLineRunner-------------------");
        for (String arg : args) {
            System.out.println(arg);
        }
        System.out.println("--------------------结束运行StartRunCommandLineRunner-------------------");
    }
}

启动后,若有传入启动参数则可以读取到。

注意,启动参数需要这样写(多个参数用空格隔开):testArg1 Arg2

示例

三、特殊需求情况下,利用监听器、回调来执行自定义任务

预计在启动期间运行的任务应该交由 CommandLineRunner  和  ApplicationRunner 组件执行,而不是使用Spring组件的生命周期回调,如 @PostConstruct

事件监听器不应该运行潜在耗时的任务,因为它们默认是在同一个线程中执行。 考虑使用 ApplicationRunner 和 CommandLineRunner 代替。

——官方文档说明

常规情况下,使用ApplicationRunnerCommandLineRunner就已经足够满足日常需求。

若是真有某个需求,一定要在Application启动的过程中执行某些任务,则可以考虑以下的几种方式。

特别说明:如果监听器要监听的事件在bean可以开始实例化前(比如BeanDefined都还没开始),请不要使用@Autowired等方式注入bean依赖项,因为你会获得null。

1、Spring bean生命周期回调(Spring Boot中自然也适用)

JSR-250的 @PostConstruct 和 @PreDestroy 注解通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注解意味着你的Bean不会被耦合到Spring特定的接口。 

——官方说明

官网文档参考:https://docs.spring.io/spring-framework/reference/core/beans/factory-nature.html

注意:这两个注解,是应用于所处的Bean上的,目标就是为这个Bean做一些额外的事情。

@Component
public class MyPostConstruct {

    /**
     * 让Bean在容器对Bean设置了所有必要的属性后执行初始化工作(此时例如可以用来执行预填充之类的行为)
     */
    @PostConstruct
    public void init() {
        System.out.println("--------------------MyPostConstruct开始运行PostConstruct-------------------");
        System.out.println("--------------------MyPostConstruct结束运行PostConstruct-------------------");
    }

    /**
     * 让Bean在包含它的容器被销毁时获得一个回调(其实一般就是关闭应用的时候)(此时例如可以用来执行清空缓存之类的行为)
     */
    @PreDestroy
    public void destroy() {
        System.out.println("--------------------MyPostConstruct开始运行PreDestroy-------------------");
        System.out.println("--------------------MyPostConstruct结束运行PreDestroy-------------------");
    }
}

2、Application发布的事件的监听器(@EventListener)

关于@EventListener:

  1. 需要Spring版本>=4.2。

  2. 只能应用于方法上。

  3. 用于监听被发布到ApplicationContext中的ApplicationEvent(本质就是观察者模式)。

官网介绍:https://docs.spring.io/spring-framework/reference/core/beans/context-introduction.html

注:下面代码示例的事件监听,是按照Spring Boot发布的顺序从早到晚放置的。(实际能监听的事件还有很多,只要是继承自ApplicationEvent都行,请自行探索)

注:如果你好奇代码里为啥标了无效二字的监听器,实际真的监听不到事件,那么,请拉到文章后面看扩展部分内容(涉及监听器注册原理)。

@Configuration
public class MyApplicationListener{

    /**
     * 监听ApplicationStarting事件(无效): 一个 ApplicationStartingEvent 在运行开始时被发布,但在任何处理之前,除了注册监听器和初始化器之外。
     */
    @EventListener
    public void applicationStartingListener(ApplicationStartingEvent applicationStartingEvent) {
        System.out.println("-----------------ApplicationStarting----------------");
    }

    /**
     * 监听ApplicationEnvironmentPrepared事件(无效): 当在上下文中使用的 Environment 已知,但在创建上下文之前,将发布 ApplicationEnvironmentPreparedEvent。
     */
    @EventListener
    public void applicationEnvironmentPreparedListener(ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
        System.out.println("-----------------ApplicationEnvironmentPrepared----------------");
    }

    /**
     * 监听ApplicationContextInitialized事件(无效): 当 ApplicationContext 已准备好并且 ApplicationContextInitializers 被调用,但在任何Bean定义被加载之前,ApplicationContextInitializedEvent 被发布。
     */
    @EventListener
    public void applicationContextInitializedListener(ApplicationContextInitializedEvent applicationContextInitializedEvent) {
        System.out.println("-----------------ApplicationContextInitialized----------------");
    }

    /**
     * 监听ApplicationPrepared事件(无效): 一个 ApplicationPreparedEvent 将在刷新开始前但在Bean定义加载后被发布。
     */
    @EventListener
    public void applicationPreparedListener(ApplicationPreparedEvent applicationPreparedEvent) {
        System.out.println("-----------------ApplicationPrepared----------------");
    }

    /**
     * 监听ApplicationContextRefreshedEvent事件
     */
    @EventListener(classes = {ContextRefreshedEvent.class}) // 允许的写法2,这里可以写多个要监听的类
    public void contextRefreshedEventListener() {
        System.out.println("-----------------ContextRefreshedEvent----------------");
    }

    /**
     * 监听ApplicationStarted事件: 在上下文被刷新之后,但在任何应用程序和命令行运行程序被调用之前,将发布一个 ApplicationStartedEvent。
     */
    @EventListener
    public void applicationStartedListener(ApplicationStartedEvent applicationStartedEvent) {
        System.out.println("-----------------ApplicationStarted----------------");
    }

    /**
     * 官方文档:应用组件可以在任何时候通过注入 ApplicationAvailability 接口并调用其上的方法来检索当前的可用性状态。
     * 一个应用程序的 “Liveness” 状态告诉我们它的内部状态是否允许它正常工作,或者在当前失败的情况下自行恢复。
     * 一个broken状态的 “Liveness” 状态意味着应用程序处于一个无法恢复的状态,基础设施应该重新启动应用程序。
     * Spring Boot应用程序的内部状态大多由Spring ApplicationContext 表示。
     * 如果 application context 已成功启动,Spring Boot就认为应用程序处于有效状态。一旦context被刷新,应用程序就被认为是活的 <br />
     */
    @EventListener
    public void onLivenessStateChange(AvailabilityChangeEvent<LivenessState> event) {
        switch (event.getState()) {
            case CORRECT:
                System.out.println("LivenessState is correct");
                break;
            case BROKEN:
                System.out.println("LivenessState is broken");
                break;
        }
    }

    /**
     * 监听ApplicationReady事件: 在任何ApplicationRunner 和 CommandLineRunner被调用后,将发布一个 ApplicationReadyEvent。
     */
    @EventListener
    public void applicationReadyListener(ApplicationReadyEvent applicationReadyEvent) {
        System.out.println("-----------------ApplicationReady----------------");
    }

    /**
     * 官方文档:应用组件可以在任何时候通过注入 ApplicationAvailability 接口并调用其上的方法来检索当前的可用性状态。
     * 应用程序的 “Readiness” 状态告诉平台,该应用程序是否准备好处理流量。failing状态的 “Readiness” 告诉平台,它暂时不应该将流量发送到该应用程序。
     * 这通常发生在启动期间,当 CommandLineRunner 和 ApplicationRunner 组件还在被处理的时候,或者是应用程序觉得目前负载已经到了极限,不能再处理额外的请求的时候。
     * 一旦 ApplicationRunner 和 CommandLineRunner 被调用完成,就认为应用程序已经准备好了
     */
    @EventListener
    public void onReadinessStateChange(AvailabilityChangeEvent<ReadinessState> event) {
        switch (event.getState()) {
            case ACCEPTING_TRAFFIC:
                System.out.println("ReadinessState is accepting traffic");
                break;
            case REFUSING_TRAFFIC:
                System.out.println("ReadinessState is refusing traffic");
                break;
        }
    }

    /**
     * 监听ApplicationFailed事件: 如果启动时出现异常,将发布一个 ApplicationFailedEvent
     */
    @EventListener
    public void applicationFailedListener(ApplicationFailedEvent applicationFailedEvent) {
        System.out.println("-----------------ApplicationFailed----------------");
    }
}

3、Application发布的事件的监听器(实现ApplicationListener接口)

这个方式其实本质上和使用@EventListener注解是一样的,也是为了能够产生监听器ApplicationListener实例去监听ApplicationEvent

存在的问题也类似,在ContextRefreshedEvent事件前的那些事件,也都是监听不到的。

原因请拉到文章后面看扩展部分内容(涉及监听器注册原理)。

但是,和@EventListener注解不一样。这个方式可以通过配置,让Application启动时提前加载自定义的类并实例化,使得可以监听到很完整的各种ApplicationEvent。

当然,如果你要监听的事件,是在ContextRefreshedEvent事件之后发布的,那么就不需要担心监听不到的事情,也不需要额外配置,直接照常实现ApplicationListener接口即可。

先看ApplicationListener接口源码:

可以看到,只要事件继承自ApplicationEvent,就都可以通过实现ApplicationListener接口来实现监听。

实现事件监听器示例(这里监听的是ApplicationContextInitializedEvent事件):

@Component
public class MyApplicationContextInitializedListener implements ApplicationListener<ApplicationContextInitializedEvent> {

    @Override
    public void onApplicationEvent(ApplicationContextInitializedEvent event) {
        System.out.println("---------------ApplicationContextInitializedEvent------------------");
    }
}

由上文可知,仅仅是靠这个类,是监听不到ApplicationContextInitializedEvent的,因为事件的发布,早于这个监听器bean实例化后真正开始监听的时间。

这时候,就需要使用配置文件spring.factories

在项目的resources文件夹下,新建一个META-INF文件夹,然后新建一个spring.factories文件

spring.factories里面写入:

org.springframework.context.ApplicationListener=com.basic.happytest.modules.startRun.MyApplicationContextInitializedListener,\
com.basic.happytest.modules.startRun.MyApplicationStartingListener

(org.springframework.context.ApplicationListener 是接口路径)

(com.basic.happytest.modules.startRun.MyApplicationContextInitializedListener是实现类的路径,也就是希望提前加载的监听器)

(多个实现类可以用英文逗号隔开,反斜线是为了可以换行书写更美观一些)

至于为什么弄一个spring.factories就可以了,请拉到文章后面看扩展部分内容(涉及原理)。

4、自实现SpringApplicationRunListeners以监听Application发布的事件

不推荐,原理可以看扩展部分的内容中,关于spring.factories配置的原因的内容。

看完其实就知道可以怎么去做了。

5、自实现Java Servlet监听器

javax.servlet.ServletContextListener:监听Java Web的ServletContext生命周期变化。

注意:这里的是ServletContext,和本文其他地方的Context是不一样的。

注意:此处实测利用@Autowired注入依赖项是OK的。(但是我网上有看到有人说不行,不是很清楚为什么)

ServeletContext和事件监听生效的时机,实际也是在Application的ContextRefresh期间,详情请拉到文章后面看扩展部分内容。

@Component
public class MyServletContextListener implements ServletContextListener {

    /**
     * ServletContext初始化后执行
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("--------------------ServletContext Initialized -------------------");
    }

    /**
     * ServletContext销毁触发,一般就是结束应用的时候
     */
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("--------------------ServletContext Destroyed -------------------");
    }

}

javax.servlet.ServletContextAttributeListener:监听Java Web的ServletContext对象共享数据变化时刻。

更多事件监听的内容,请自行探索。

四、扩展

1、@EventListener注解的监听器生效时机及原理说明

@EventListener注解的处理是通过内部 EventListenerMethodProcessor bean 执行的。

EventListener.java

此外,EventListenerMethodProcessor继承自BeanFactoryPostProcessor接口,实现的postProcessBeanFactory方法,在容器将所有bean解析成beanDefinition后,实例化之前执行(方法内容是:先获取所有实现EventListenerFactory接口的单例对象,排序后保存在容器中)。

当所有单例 bean 都初始化完成以后,Spring的IOC容器会回调实现SmartInitializingSingleton接口的EventListenerMethodProcessor 的 afterSingletonsInstantiated()方法。afterSingletonsInstantiated方法本身在各种判断后,会调用processBean方法。processBean方法会注册@EventListener注解的方法作为独立的ApplicationListener实例,并关联到ApplicationContext的initialMulticaster(事件广播器)上,这之后,@EventListener注解的监听器才会开始监听事件。

所以,不妨找找源码里,什么时候执行afterSingletonsInstantiated()方法。

也就知道具体什么时候@EventListener注解的监听器才会开始生效。

让我们回到SpringApplication.run方,这个启动开始的地方。

SpringApplication.java

进入到refreshContext(context);方法。

SpringApplication.java

继续深入。

SpringApplication.java

一路找到ConfigurableApplicationContext接口实现类AbstractApplicationContext里的refresh方法。

AbstractApplicationContext.java

我们可以看到refresh方法里有一个finishBeanFactoryInitialization(beanFactory);的环节,注释写有singletons,所以进去看看实现。

AbstractApplicationContext.java

继续进入到beanFactory.preInstantiateSingletons()里面,可以找到ConfigurableListableBeanFactory接口的实现类DefaultListableBeanFactory。

DefaultListableBeanFactory.java

这里面,我们可以看到,先是在那里实例化bean,然后是执行了afterSingletonsInstantiated方法。

前文我们提到,EventListenerMethodProcessor 的 afterSingletonsInstantiated方法本身在各种判断后,会调用processBean方法。processBean方法会注册@EventListener注解的方法作为独立的ApplicationListener实例,并关联到ApplicationContext的initialMulticaster(事件广播器)上。

这时候,我们再回过头,就会发现,实际上afterSingletonsInstantiated方法如果执行完了,立马就是发布ContextRefreshedEvent事件。

那么,可以知道,在ContextRefreshedEvent事件前的那些事件,都是监听不到的。

2、配置spring.factories后自定义ApplicatonListener实现类就能提前注册的原因

让我们回到SpringApplication.java中,先看看SpringApplication的构造方法。

SpringApplication.java

可以看到,在Spring(Spring Boot)启动流程中,在初始化SpringApplication对象时,会先通过SPI方式获取所有的ApplicationListener的实现类。

SpringApplication.java

实际就是读取classPath路径所有META-INF/spring.factories文件,来找ApplicationListener的实现类(在这里说的spring.factories,因为是Spring Boot项目,所以实际上是spring-boot的jar包里的对应文件)。

我们自己建一个META-INF/spring.factories文件,为的就是在这个时候,让自定义的ApplicationListener实现类也被加载进去。

那么,为什么在这里被加载进来,就会生效呢?

继续来看看Application启动时就执行的的run方法。

SpringApplication.java

可以看到箭头指向的内容——获取SpringApplicationRunListeners的实例对象。

进去查看【获取SpringApplicationRunListeners】的对象方法的实现。

SpringApplication.java

可以发现,【SpringApplicationRunListeners的实例对象】的来源,也是通过SPI方式获取所有的SpringApplicationRunListener的实现类。

查看这个loadFactoryNames方法实现。其中注释说明是重点。

SpringFactoriesLoader

我们可以在外部库里找到spring.factories

可以看到Spring Boot的spring.factories

Spring Boot包

Spring Boot的spring.factories里面可以找到SpringApplicationRunListener

Spring Boot的spring.factories

这里可以发现,加载的【SpringApplicationRunListeners的实例对象】实际就是EventPublishingRunListener。

EventPublishingRunListener的构造函数,也就是实例化时,是会依据入参,去获取SpringAppliction所有监听器(实际就是前文提及的SpringApplication初始化时去加载的ApplicationListener实现类)关联到initialMulticaster(事件广播器)上。

EventPublishingRunListener

EventPublishingRunListener其实就是进行发布各种SpringApplicationEvents的,Spring Boot启动过程中的Application事件就会从这里用initialMulticaster(事件广播器)发出,并由与其相关联的监听器接收。

比如run方法里:

SpringAppliction.java

至此,一切都清晰明了了。

我们去整一个spring.factories,为的就是让SpringApplication初始化时把我们【自定义的ApplicationListener实现类】给加载了。让其在实例化【SpringApplicationRunListeners的实例对象】时,能够把【自定义的ApplicationListener实现类】给关联到initialMulticaster(事件广播器)上面。使得可以监听到启动过程中完整的事件。

(其实这里实际也可以看出来,还有一种偏门的监听方式,就是去自实现SpringApplicationRunListeners,并配置到spring.factories里,这样也行

3、自实现ApplictionListener监听器的注册时机(生效时机)

让我们再次回到SpringApplication.run方法,这个启动开始的地方。

SpringApplication.java

进入到refreshContext(context);方法。

SpringApplication.java

继续深入。

SpringApplication.java

一路找到ConfigurableApplicationContext接口实现类AbstractApplicationContext里的refresh方法。

AbstractApplicationContext.java

可以看到有一个注册监听器的环节:registerListeners();

进入方法内部看看。

AbstractApplicationContext.java

方法注释已经说明了一切,我们自定义的ApplicationListener实现类(可能还有其他的不是我们自定义的监听器),如果没有去配置spring.factories,就会在此时被找到并实例化,然后关联到ApplicationContext的initialMulticaster(事件广播器)上,开始监听事件。

(你可能会注意到源码底下的这个Publish early application events,但是实际在refresh方法的最开始,prepareRefresh()是将earlyApplicationEvents设置为空的。所以即便真有事件,也是refresh里产生的。更早的外部的,是没有的。)

4、Spring Boot关于ServletContext的初始化和ServletListener相关事件监听器生效

让我们又又又一次回到SpringApplication.run方法,这个启动开始的地方。

SpringApplication.java

进入到refreshContext(context);方法。

SpringApplication.java

继续深入。

SpringApplication.java

一路找到ConfigurableApplicationContext接口实现类AbstractApplicationContext里的refresh方法。

AbstractApplicationContext.java

这次,我们要看的是onRefresh(),而且是实现类ServletWebServerApplicationContext的具体实现

ServletWebServerApplicationContext.java

进入createWebServer方法。

ServletWebServerApplicationContext.java

可以看到,这里就是在创建/初始化ServletContext。

ServletWebServerApplicationContext.java

让我们先进入这个获取自我初始化方法getSelfInitializer()中

ServletWebServerApplicationContext.java

可以看到,getServletContextInitializerBeans() 在获取ServletContxt初始化相关的bean。

进去瞧瞧。

ServletWebServerApplicationContext.java

这里我们就找到了,原来不仅仅是EventListener,还有其他的东西,都是这个时候实例化后收集的。

再进去看看细节。

ServletContextInitializerBeans.java

addAdaptableBeans()值得留意

ServletContextInitializerBeans.java

终于,我们找到了EventListener,但是这里还没完,因为这个类只是其收集作用,真正注册监听器还没到时候。

接下来,进入getSelfInitializer().onStartup(servletContext)的onStartup

选择ServletContextInitializer接口实现类RegistrationBean

RegistrationBean.java

选择子类ServletListenerRegistrationBean(Filter啥的就是其他的RegistrationBean子类)

ServletListenerRegistrationBean.java

此时,可以看到ServletListenerRegistrationBean就是专门注册ServletContext的相关监听器的。

ServletListenerRegistrationBean.java

至此,ServletContext的初始化位置和ServletListener相关事件监听器注册,都已经找到。

我们也就能知道,ServletContext初始化是在ApplicationContext refresh的时候,ServletContext相关的监听器也是初始化的时候一并注册。

五、后话

实际上,关于@EventListener、ApplicationListener。其实我们实际开发过程中,更多时候并不是用来在Spring Boot(或Spring)的Application启动过程执行自定义任务。而是去自实现ApplicationEvent,将事件发布到上下文中,然后用监听器去监听发布的事件,做相应的处理。(即观察者模式)



Comment