前言

SpringApplication从创建到销毁的完整生命周期中,会在不同阶段进行广播不同的事件,我们可以选择监听特定事件,并进行相应的操作.

事件监听的三种方式

1.使用@EventListener注解

新建一个自定义的Listener,代码如下:

package geek.springboot.application.listener;  
  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.boot.context.event.ApplicationStartedEvent;  
import org.springframework.context.event.EventListener;  
import org.springframework.stereotype.Component;  
  
/**  
* 自定义SpringApplication事件监听  
*/  
@Slf4j  
@Component  
public class CustomListener {  
  
    /**  
    * 在Bean的某个方法添加上@EventListener注解,则SpringApplication在进行事件分发的时候,会主动回调该方法  
    *  
    * @param event 事件  
    * 这里以监听{@link ApplicationStartedEvent}事件为例,该事件在SpringApplication启动完成时会进行广播  
    */
    @EventListener  
    public void handleEvent(ApplicationStartedEvent event) {  
        log.info("handle event {}", event.toString());  
    }  
  
}

启动应用,控制台输出如下,可以看到事件监听成功:

SpringBoot核心特性——应用事件监听_自定义

当然也可以在@EventListener上指定该注解标记的方法都监听哪些事件,代码稍作改动:

/**  
* 注解参数说明:  
* classes:为该方法所监听的事件class数组,这里表示当前方法只监听ApplicationStartedEvent和ApplicationReadyEvent  
* value: 为该方法所监听的事件的class,当前方法仅监听某个特定事件时,可以设置value  
* condition: 支持通过设置一段SpEL来计算出当前事件是否要被监听,当表达式返回"true","on","1","yes"任一值时,表示监听当前事件  
* <p>  
* 这里方法形参的类型调整为SpringApplicationEvent,因为它是ApplicationStartedEvent和ApplicationReadyEvent的父类  
*/  
@EventListener(classes = {ApplicationStartedEvent.class, ApplicationReadyEvent.class})  
public void handleEvent(SpringApplicationEvent event) {  
log.info("handle event {}", event.toString());  
}

重启应用,查看控制台输出,两种事件都成功监听:

SpringBoot核心特性——应用事件监听_spring_02

2.spring.factories注册listener

在项目resources目录下的META-INF/spring.factories文件中,添加如下代码:

org.springframework.context.ApplicationListener=\geek.springboot.application.listener.CustomListener

同时CustomListener不再使用@EventListener,而是实现ApplicationListener接口.

package geek.springboot.application.listener;  
  
import lombok.extern.slf4j.Slf4j;   
import org.springframework.boot.context.event.ApplicationStartedEvent;  
import org.springframework.boot.context.event.SpringApplicationEvent;  
import org.springframework.context.ApplicationListener;  
import org.springframework.stereotype.Component;  
  
/**  
* 自定义SpringApplication事件监听  
*/  
@Slf4j  
@Component  
public class CustomListener implements ApplicationListener<SpringApplicationEvent> {  
  
    /**  
    * 事件回调方法  
    *  
    * @param event 监听的事件{@link SpringApplicationEvent}  
    */
    @Override  
    public void onApplicationEvent(SpringApplicationEvent event) {  
        log.info("handle application event {}", event.toString());  
    }  
}

重启项目,查看控制台输出,事件监听成功.

SpringBoot核心特性——应用事件监听_事件监听_03

3. SpringApplication添加listener

代码如下:

package geek.springboot.application;  
  
import geek.springboot.application.listener.CustomListener;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
  
@Slf4j  
@SpringBootApplication  
public class Application {  
  
    public static void main(String[] args) {  
        SpringApplication application = new SpringApplication(Application.class);  
        // 添加listener  
        application.addListeners(new CustomListener());  
        application.run(args);  
    }  
  
}

控制台输出如下,It works:

SpringBoot核心特性——应用事件监听_事件监听_04

同时也可以使用SpringApplicationBuilder构造的方式,代码如下:

public static void main(String[] args) {  
    SpringApplicationBuilder builder = new SpringApplicationBuilder();  
    // 添加listener  
    builder.listeners(new CustomListener())  
    // 启动  
    .build().run();  
}

常用的事件

事件

广播时机

ApplicationStartingEvent

SpringApplication启动的时候,此时ApplicationContext未创建

ApplicationEnvironmentPreparedEvent

环境参数初始化完毕的时候

ApplicationContextInitializedEvent

ApplicationContext准备好了的时候,此时Bean定义未加载

ApplicationPreparedEvent

refresh()方法调用前,此时Bean定义已加载

ApplicationStartedEvent

refresh()方法调用后,回调ApplicationRunner和CommandLineRunner前

ApplicationReadyEvent

SpringApplication启动完毕后,ApplicationRunner和CommandLineRunner回调后

ApplicationFailedEvent

SpringApplication启动失败时

ContextRefreshedEvent

ApplicationContext刷新时

为什么添加的ApplicationListener不生效?

1. 监听的事件早于Bean创建

代码如下:

package geek.springboot.application.listener;  
  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.boot.context.event.ApplicationStartingEvent;  
import org.springframework.context.event.EventListener;  
import org.springframework.stereotype.Component;  
  
/**  
* 无效的Listener  
*  
* @author Bruse  
*/  
@Slf4j  
@Component  
public class InvalidListener {  
  
    /**  
    * 这里尝试监听ApplicationStartingEvent,但是启动应用是会发现log并没有输出  
    *  
    * @param event {@link ApplicationStartingEvent}  
    */  
    @EventListener(value = ApplicationStartingEvent.class)  
    public void handleEvent(ApplicationStartingEvent event) {  
        log.info("handle event: {}", event.getClass().getSimpleName());  
    }  
  
}

具体原因是,因为ApplicationStartingEvent广播时机是在Bean初始化之前,这个时候InvalidListener甚至都还没被初始化,所以handleEvent()没有被回调.

2. 此SpringApplication非彼SpringApplication

关键代码如下,启动后会发现控制台是没有相应的log输出的:

@Slf4j  
@SpringBootApplication  
public class Application {  
  
    public static void main(String[] args) {  
        // 1.初始化SpringApplication
        SpringApplication springApplication = new SpringApplication();  
        // 2.添加listener
        springApplication.addListeners(new CustomListener());  
        // 3.启动SpringApplication
        springApplication.run(Application.class, args);  
    }  
  
}

关键在于 3.启动SpringApplication 这一行

首先addListeners()是往SpringApplication中添加一个listener,源码如下:

SpringBoot核心特性——应用事件监听_自定义_05

逻辑很简单,listener也应该被添加到SpringApplication的内部属性listeners中了

但是再来看看run(Application.class, args)的源码:

SpringBoot核心特性——应用事件监听_自定义_06

SpringBoot核心特性——应用事件监听_spring_07

到这里就很明显了,首先springApplication.run(Application.class, args);这一行代码,调用的是SpringApplication的静态run(),然后它实际上是会创建一个新的SpringApplication,并调用run(args)方法.

也就是说,我们手动创建了SpringApplication实例A,listener是添加到了实例A中,但是执行 3.启动SpringApplication 这行代码时,会创建出一个新的SpringApplication实例B.最后真正run起来的其实是实例B,所以我们自定义的listener事件监听会不起作用,因为A压根没运行.

解决方法很简单,调用正确的run()即可.

public static void main(String[] args) {  
    // 初始化SpringApplication  
    SpringApplication springApplication = new SpringApplication(Application.class);  
    // 添加listener  
    springApplication.addListeners(new CustomListener());  
    // 正确的run  
    springApplication.run(args);  
}

当心启动迟缓

因为广播事件和回调EventListener的线程,与执行SpringApplication.run()的方法是同一个,所以尽量不要在自定义的事件监听方法里做比较耗时的操作,这样会使得整个SpringApplication启动变慢.

源码分析

简简单单看一下SpringBoot是怎么广播事件的,首先进入到run()的实现

SpringBoot核心特性——应用事件监听_事件监听_08

看到熟悉的listener单词,但是这个listener并不是我们熟知的ApplicationListener

SpringBoot核心特性——应用事件监听_spring_09

深入查看一下listeners.starting()的实现,看看SpringApplicationRunListeners和ApplicationListener之间是否存在啥关系

SpringBoot核心特性——应用事件监听_spring_10

可以看到SpringApplicationRunListeners内部存在一个SpringApplicationRunListener列表,start()内部其实就是逐个调用SpringApplicationRunListener的start()方法.

SpringApplicationRunListeners与SpringApplicationRunListener两个是不同的类,区别是一个类名多了个s,一个类名少了s,别混淆.

SpringBoot核心特性——应用事件监听_spring_11

继续探索SpringApplicationRunListener.run(),最后终于一路找到了SimpleApplicationEventMulticaster类的multicastEvent方法,在这里获取所有的ApplicationListener,并逐个遍历进行操作.

SpringBoot核心特性——应用事件监听_自定义_12

最后继续查看invokeListener()的实现:

SpringBoot核心特性——应用事件监听_spring_13

这里可以看出,事件广播整体逻辑其实就是在SpringApplicaiton运行期间,通过调用SpringApplicationRunListener的不同方法,创建不同的事件,然后交由SimpleApplicationEventMulticaster处理.

而SimpleApplicationEventMulticaster的操作无非是:

  1. 获取到所有的ApplicationListener,并遍历
  2. 调用每个listener的onApplicationEvent()方法,实现事件广播