前言
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());
}
}
启动应用,控制台输出如下,可以看到事件监听成功:
当然也可以在@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());
}
重启应用,查看控制台输出,两种事件都成功监听:
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());
}
}
重启项目,查看控制台输出,事件监听成功.
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:
同时也可以使用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,源码如下:
逻辑很简单,listener也应该被添加到SpringApplication的内部属性listeners中了
但是再来看看run(Application.class, args)的源码:
到这里就很明显了,首先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()的实现
看到熟悉的listener单词,但是这个listener并不是我们熟知的ApplicationListener
深入查看一下listeners.starting()的实现,看看SpringApplicationRunListeners和ApplicationListener之间是否存在啥关系
可以看到SpringApplicationRunListeners内部存在一个SpringApplicationRunListener列表,start()内部其实就是逐个调用SpringApplicationRunListener的start()方法.
SpringApplicationRunListeners与SpringApplicationRunListener两个是不同的类,区别是一个类名多了个s,一个类名少了s,别混淆.
继续探索SpringApplicationRunListener.run(),最后终于一路找到了SimpleApplicationEventMulticaster类的multicastEvent方法,在这里获取所有的ApplicationListener,并逐个遍历进行操作.
最后继续查看invokeListener()的实现:
这里可以看出,事件广播整体逻辑其实就是在SpringApplicaiton运行期间,通过调用SpringApplicationRunListener的不同方法,创建不同的事件,然后交由SimpleApplicationEventMulticaster处理.
而SimpleApplicationEventMulticaster的操作无非是:
- 获取到所有的ApplicationListener,并遍历
- 调用每个listener的onApplicationEvent()方法,实现事件广播