七、Spring事件常见错误
一.试图处理并不会抛出的事件(并不是所有的事件都执行了start方法,只有执行了start方法的事件,才可以被监听到)
Spring的事件模型
1、Spring 事件的三大组件
1.事件(Event)
用来区分和定义不同的事件,在 Spring 中,常见的如 ApplicationEvent 和 AutoConfigurationImportEvent,它们都继承于 java.util.EventObject。
2.事件广播器(Multicaster)
负责发布上述定义的事件。例如,负责发布 ApplicationEvent 的 ApplicationEventMulticaster 就是 Spring 中一种常见的广播器。
3.事件监听器(Listener)
负责监听和处理广播器发出的事件,例如 ApplicationListener 就是用来处理 ApplicationEventMulticaster 发布的 ApplicationEvent,它继承于 JDK 的 EventListene。
2、代码
@Slf4j
@Component
public class MyContextStartedEventListener implements ApplicationListener<ContextStartedEvent> {
public void onApplicationEvent(final ContextStartedEvent event) {
log.info("{} received: {}", this.toString(), event);
}
}
这段代码定义了一个监听器 MyContextStartedEventListener,试图拦截 ContextStartedEvent。
ContextStartedEvent的抛出只发生在一处,位于方法AbstractApplicationContext.start()之中
@Override
public void start() {
getLifecycleProcessor().start();
publishEvent(new ContextStartedEvent(this));
}
也就是说,只有上述方法被调用,才会抛出 ContextStartedEvent。
Spring 启动方法中围绕 Context 的关键方法调用,代码如下:
public ConfigurableApplicationContext run(String... args) {
//省略非关键代码
ConfigurableApplicationContext context = null;
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);此处知识进行了刷新操作
afterRefresh(context, applicationArguments);
//省略非关键代码
Spring 启动最终调用的是 AbstractApplicationContext.refresh,并不AbstractApplicationContext.start。在这样的残酷现实下,ContextStartedEvent 自然不会被抛出,不抛出,自然也不可能被捕获。所以这样的错误也就自然发生了。
3、问题修正
1. 误读事件(监听错了事件)
误以为,想要监听的事件是自己想要的事件,比如关于Context,关于context的真是调用其实是刷新操作,因此我们真正需要调用的不是start,而是refresh.
@Component
public class MyContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> {
public void onApplicationEvent(final ContextRefreshedEvent event) {
log.info("{} received: {}", this.toString(), event);
}
}
监听 ContextRefreshedEvent 而非 ContextStartedEvent。ContextRefreshedEvent 的抛出可以参考方法 AbstractApplicationContext.finishRefresh,它本身正好是 Refresh 操作中的一步。
protected void finishRefresh() {
//省略非关键代码
initLifecycleProcessor();
// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();
// Publish the final event.
publishEvent(new ContextRefreshedEvent(this));
//省略非关键代码
}
2. 需要处理该事件(就是要监听这个是事件,哪怕是进行手动的调用)
这种情况下,我们真的需要去调用 AbstractApplicationContext#start 方法。
@RestController
public class HelloWorldController {
@Autowired
private AbstractApplicationContext applicationContext;
@RequestMapping(path = "publishEvent", method = RequestMethod.GET)
public String notifyEvent(){
applicationContext.start();
return "ok";
};
}
当一个事件拦截不了时,我们第一个要查的是拦截的事件类型对不对,执行的代码能不能抛出它。
二.监听事件的体系不对
1、代码:处理ApplicationEnvironmentPreparedEvent 事件
@Slf4j
@Component
public class MyApplicationEnvironmentPreparedEventListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent > {
public void onApplicationEvent(final ApplicationEnvironmentPreparedEvent event) {
log.info("{} received: {}", this.toString(), event);
}
}
经过案例一的经验,首先看这个事件的抛出有没有问题。如果抛出有问题,那么必然是无法监听到的。
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
2、案例分析(定义的监听器并不能监听到 initialMulticaster 广播出的 ApplicationEnvironmentPreparedEvent)
这是在 Spring 事件处理上非常容易犯的一个错误,即监听的体系不一致。
关于 ApplicationEnvironmentPreparedEvent 的处理,它相关的两大组件是什么?
1.广播器
这个事件的广播器是 EventPublishingRunListener 的 initialMulticaster,代码参考如下:
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
//省略非关键代码
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
//省略非关键代码
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
}
2.监听器
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
个事件的监听器就存储在 SpringApplication#Listeners 中,调试下就可以找出所有的监听器,但是自定义的监听器并不存在在其中。