Spring事件机制介绍
Spring的事件通知机制是一项很有用的功能,使用事件机制我们可以将相互耦合的代码解耦,从而方便功能的修改与添加;
spring事件分为3步:
- 事件定义 (事件定义:需要继承:
ApplicationEvent
) - 事件监听 (事件监听:需要实现:
ApplicationListener
) - 事件发布
发布一个自定义的事件,会触发该事件监听器的onApplicationEvent
方法
Spring为我们提供了一些已经定义好了的事件:
ApplicationContextEvent
ContextClosedEvent
当容器关闭时候会触发该事件的监听器ContextRefreshedEvent
当容器refresh完成后会触发该事件的监听器ContextStartedEvent
当容器启动会触发该事件的监听器ContextStoppedEvent
当容器关闭的时候会触发该事件的监听器ServletRequestHandledEvent
Spring MVC 请求完成之后推送会触发该事件监听器
Spring自定义事件通知
- 1.自定义事件:
public class DemoEvent extends ApplicationEvent {
private String message;
public DemoEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage(){
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
}
- 2.定义事件监听器:
监听器1:
@Component
@Order(1)
public class DemoListener implements ApplicationListener<DemoEvent> {
@Override
public void onApplicationEvent(DemoEvent event) {
System.out.println("当前线程 " + Thread.currentThread().getName() + ": receiver : " + event.getMessage());
event.setMessage(event.getMessage() + "@1");
}
}
监听器2:
@Component
@Order(5)
public class Demo2Listener implements ApplicationListener<DemoEvent> {
@Override
public void onApplicationEvent(DemoEvent event) {
System.out.println("当前线程 " + Thread.currentThread().getName() + ": receiver 2: " + event.getMessage());
}
}
@Order
注解作用:当监听器被触发时,监听器的执行顺序,order值越小越先执行;
- 3.事件发布:
@Component
public class DemoPublish {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void publish(String message) {
//执行相关的业务逻辑
System.out.println("当前线程 " + Thread.currentThread().getName() + ": ");
DemoEvent demoEvent = new DemoEvent(this, message);
applicationEventPublisher.publishEvent(demoEvent);
}
}
- 4.单元测试:
@SpringBootTest
class DemoApplicationTests {
@Autowired
private DemoPublish demoPublish;
@Test
public void testPublish(){
demoPublish.publish("hello world");
}
}
- 5.运行结果:
当前线程 main:
当前线程 main: receiver : hello world
当前线程 main: receiver 2: hello world@1
如果我们需要在执行相关业务逻辑后处理一些其他相关的操作,就可以再增加一个该事件的监听器;
通过运行结果:我们不难发现,该事件都是同步操作的;如果在一些业务场景中,我们希望个别监听器的业务逻辑,不要阻塞主流程的执行,我们可以采用异步的方式:如下。
异步事件监听
在上部分的基础上:
- 1.添加一个配置类:
@Configuration
public class DemoConfiguration {
/**
* 容器中注册一个 线程池
* @return
*/
@Bean
public SimpleAsyncTaskExecutor simpleAsyncTaskExecutor(){
return new SimpleAsyncTaskExecutor();
}
/**
* 自定义一个 ApplicationEventMulticaster 在容器中
* @param simpleAsyncTaskExecutor
* @return
*/
@Bean
public ApplicationEventMulticaster applicationEventMulticaster(SimpleAsyncTaskExecutor simpleAsyncTaskExecutor){
SimpleApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();
applicationEventMulticaster.setTaskExecutor(simpleAsyncTaskExecutor);
return applicationEventMulticaster;
}
}
- 2.运行单元测试,输出的结果:
当前线程 main:
当前线程 SimpleAsyncTaskExecutor-14: receiver : hello world
当前线程 SimpleAsyncTaskExecutor-15: receiver 2: hello world@1
由于跑的是单元测试,可能会涉及到主线程结束,所有子线程都挂掉的情况;
通过运行结果可以发现:DemoListener、Demo2Listener 都在不同的线程中执行;
如果就简单这样处理,会出现一个问题:当DemoConfiguration
被加载到容器后,所有的监听器都变成异步的了;也就说所有的事件都被异步化了; 解决此类问题方式如下:
- 3.新增加一个监听器:
@Component
@Order(3)
public class Demo3Listener implements ApplicationListener<DemoEvent> {
@Async
@Override
public void onApplicationEvent(DemoEvent event) {
System.out.println("当前线程 " + Thread.currentThread().getName() + ": receiver 3: " + event.getMessage());
}
}
可以看出,在onApplicationEvent
方法上添加了一个@Async
的注解,加上这个注解的方法在另外的线程中执行, 另外的线程就是从配置类中注入的线程池中获取;
- 4.修改配置类
DemoConfiguration
:
@Configuration
@EnableAsync
public class DemoConfiguration {
/**
* 容器中注册一个 线程池
* @return
*/
@Bean
public SimpleAsyncTaskExecutor simpleAsyncTaskExecutor(){
return new SimpleAsyncTaskExecutor();
}
}
可以看出,在配置类上添加了一个@EnableAsync
注解,该注解就是开启异步执行;并且删除了自定义ApplicationEventMulticaster
- 5.运行单元测试,查看输出结果:
当前线程 main:
当前线程 main: receiver : hello world
当前线程 main: receiver 2: hello world@1
当前线程 SimpleAsyncTaskExecutor-1: receiver 3: hello world@1
由于跑的是单元测试,可能会涉及到主线程结束,所有子线程都挂掉的情况
Spring 事件机制通知原理分析
- 1.事件发布中调用
applicationEventPublisher.publishEvent(demoEvent);
的publishEvent
方法源码如下:
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
}
}
// Multicast right now if possible - or lazily once the multicaster is initialized
// earlyApplicationEvents 为null
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
// 获取ApplicationEventMulticaster,调用`multicastEvent`方法广播事件
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
- 2.
multicastEvent
方法:
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
//获取目标执行线程池
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
//如果线程池不为null,就采用异步方式触发事件监听器
executor.execute(() -> invokeListener(listener, event));
}
else {
//否则就采用主线程触发事件监听器
invokeListener(listener, event);
}
}
}
注意:在异步监听器事件通知中,采用自定义ApplicationEventMulticaster ,并给该它设置一个线程池;因此在该配置类被加载后,就会导致所有的事件都被异步化了; 最后通过启用异步操作:@EnableSync
注解 + @Sync
注解的方式实现个别事件监听器进行异步操作,该方式的原理就是对标记了有@Sync
注解的方法进行代理,采用线程池的方式执行标记了@Sync
方法。
参考: https://blog.wangqi.love/articles/Java/Spring%20Event%E4%BA%8B%E4%BB%B6%E9%80%9A%E7%9F%A5%E6%9C%BA%E5%88%B6.html