Spring事件机制

Spring上下文启动的时候将实现ApplicationListener接口的Bean添加到事件监听者列表中,每次使用ApplicationEventPublisher发布ApplicationEvent时,都会通知对该事件感兴趣(监听该事件)的Bean。

ApplicationContext继承了ApplicationEventPublisher接口,从而拥有事件发布的能力。但是实际ApplicationContext事件发布委托给ApplicationEventMulticaster执行。

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
// ...省略
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// ...省略
}
复制代码

相关关键类或注解

  • ApplicationEventPublisher:发布事件
  • ApplicationListener:事件监听者
  • ApplicationEvent:事件
  • EventListener:事件和事件监听绑定
  • ApplicationEventMulticaster:发布事件

Spring容器在启动的过程中也会发布各种事件,相应的组件监听到之后完成各自的初始化工作,下面是Spring内置的事件。

  • ContextStartedEvent:Spring上下文启动事件
  • ContextRefreshedEvent:Spring上下文初始化或刷新事件
  • ContextStoppedEvent:Spring上下文停止事件
  • ContextClosedEvent:Spring上下文关闭事件

一、基于ApplicationListener实现事件监听

  1. 首先定一个事件,事件一定要继承ApplicationEvent
public class DemoEvent extends ApplicationEvent {
public DemoEvent(Object source) {
super(source);
}
}
复制代码
  1. 然后定义一个类实现ApplicationListener接口
@Component
public class DemoListener implements ApplicationListener<DemoEvent> {
@Override
public void onApplicationEvent(DemoEvent event) {
System.out.println("收到消息: " + event);
}
}
复制代码
  1. 发布事件
applicationContext.publishEvent(new DemoEvent(new Object()));
// 收到消息: com.example.demo.applicationevent.DemoEvent[source=java.lang.Object@3cb173db]
复制代码

虽然完成了事件监听,但是这种实现方案有点不太好,监听类必须实现特定的ApplicationListener接口,事件也必须继承ApplicationEvent类且一个类只能处理一个事件。接下来介绍第二种方式可以完全避免这种问题。

二、基于@EventListener实现事件监听

public class DemoEvent2 {
}
@Component
public class DemoListener2 {
@EventListener(DemoEvent2.class)
public void onDemoEvent(DemoEvent2 demoEvent2) {
System.out.println("@EventListener: " + demoEvent2);
}
}
// 输出日志
2020-04-28 23:52:59.187  INFO 4425 --- [           main] com.example.demo.DemoApplication         : 发布DemoEvent2事件
2020-04-28 23:52:59.187  INFO 4425 --- [           main] c.e.demo.eventlistener.DemoListener2     : @EventListener: com.example.demo.eventlistener.DemoEvent2@75d7297d
复制代码

基于@EventListener的监听类不需要实现ApplicationListener,事件也不需要继承ApplicationEvent类,且可以在类里面声明多个方法处理不同的事件。实际开发中更倾向于这种实现方案。

三、异步监听-@Async

从上面的输出日志中可以看出,事件发布和监听是处在同一个线程中,有时候我们可能需要实现异步监听,可以借助@Async和自定义ApplicationEventMulticaster两种方式实现消息的异步监听。

  1. @Async

修改代码如下

@Slf4j
@Component
@EnableAsync
public class DemoListener2 {
@Async
@EventListener(DemoEvent2.class)
public void onDemoEvent(DemoEvent2 demoEvent2) {
log.info("@EventListener: " + demoEvent2);
}
}
// 输出日志
2020-04-28 23:56:30.252  INFO 4508 --- [           main] com.example.demo.DemoApplication         : 发布DemoEvent2事件
2020-04-28 23:56:30.310  INFO 4508 --- [         task-1] c.e.demo.eventlistener.DemoListener2     : @EventListener: com.example.demo.eventlistener.DemoEvent2@184bc84
复制代码

添加注解启用异步,并在指定方法上面添加@Async注解。从日志中可以看出事件发布和监听已经处在两个不同的线程中。

  1. 自定义ApplicationEventMulticaster
@Slf4j
@Component
//@EnableAsync
public class DemoListener2 {
//    @Async
@EventListener(DemoEvent2.class)
public void onDemoEvent(DemoEvent2 demoEvent2) {
log.info("@EventListener: " + demoEvent2);
}
@Bean("applicationEventMulticaster")
public ApplicationEventMulticaster applicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor("async-"));
// 异常处理
eventMulticaster.setErrorHandler(new ErrorHandler() {
@Override
public void handleError(Throwable t) {
log.error("事件监听异常", t);
}
});
return eventMulticaster;
}
}
// 输出日志
2020-04-29 00:01:27.733  INFO 4624 --- [           main] com.example.demo.DemoApplication         : 发布DemoEvent2事件
2020-04-29 00:01:27.735  INFO 4624 --- [       async-16] c.e.demo.eventlistener.DemoListener2     : @EventListener: com.example.demo.eventlistener.DemoEvent2@56051dd7
复制代码

从日志中可以看出,通过自定义ApplicationEventMulticaster方式也同样完成事件发布和监听处在两个不同的线程。

为什么声明一个这样的Bean就可以完成异步呢?

在开始的时候提到过ApplicationContext将事件发布委托给ApplicationEventMulticaster执行,接下来通过源码看ApplicationContext如何委托事件给ApplicationEventMulticaster。

AbstractApplicationContext获取ApplicationEventMulticaster对象

ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
if (this.applicationEventMulticaster == null) {
throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
"call 'refresh' before multicasting events via the context: " + this);
}
return this.applicationEventMulticaster;
}
复制代码

接下来看AbstractApplicationContext如何初始化applicationEventMulticaster对象

public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
try {
// 省略无关代码
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
}
catch (BeansException ex) {
}
}
}
复制代码

可以看出来在调用refresh的时候会初始化applicationEventMulticaster

public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isTraceEnabled()) {
logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
}
}
}
复制代码

初始化的是首先会尝试从Spring获取指定name的Bean,如果没有获取到,则新建一个实例,并注册到IoC容器中,到这里就明白为什么声明一个Bean之后就完成了异步的操作。因为我们提前声明了一个applicationEventMulticaster Bean对象,所以Spring会把这个对象当成默认的事件发布工具。自定义对象指定了线程池,所以事件发布和监听会处在不同的线程池中。

这种做法会导致由该对象发布的所有事件都是异步处理,实际开发过程中推荐使用@Async注解实现异步监听逻辑,这样可以针对性对指定事件监听异步处理。

稀土掘金
我还没有学会写个人说明!
上一篇

Kafka数据每5分钟同步到Hive

下一篇

【天枢实验室】基于图的技术在企业威胁评估中的应用

你也可能喜欢

评论已经被关闭。

插入图片