本文仅供学习交流使用,侵权必删。
不作商业用途,转载请注明出处
1. 概述
在Spring Framework中,ApplicationContext是通过org.springframework.context.ApplicationEvent
和org.springframework.context.ApplicationListener
管理事件。一个Bean如果实现了ApplicationListener接口,这个Bean就会监听指定类型的事件,只要这个指定事件在ApplicationContext中被发布,这个监听器就会被通知。这是一种典型的观察者模式实现
当前使用版本是Spring Framework 5.2.2.RELEASE
1. 1Spring的内建事件
事件类型 | 说明 |
---|---|
ContextRefreshedEvent | 当ApplicationContext初始化完成的时候会发布该事件 |
ContextStartedEvent | 当ApplicationContext通过org.springframework.context.ConfigurableApplicationContext#start 方法启动会发布该事件 |
ContextStoppedEvent | 当ApplicationContext通过org.springframework.context.ConfigurableApplicationContext#stop 方法停止容器会发布该事件 |
ContextClosedEvent | 当ApplicationContext通过org.springframework.context.ConfigurableApplicationContext#close 方法或者通过JVM的shutdown hook关闭容器的会发布该事件 |
RequestHandledEvent | web环境事件,当一个http请求完成后发布该事件。注意这个事件只有使用了Spring的org.springframework.web.servlet.DispatcherServlet 的web程序下才能发布 |
ServletRequestHandledEvent | RequestHandledEvent的子类,扩展了一下关于Servlet上下文信息 |
除此之外,还能能够通过继承org.springframework.context.ApplicationEvent
实现自定义事件。下面将展示如何在Spring环境中实现事件的监听以及发布自定义事件。
2. 事件监听以及发布的代码实现
org.springframework.context.ApplicationListener
监听器注册到应用上下文的方式有多种:
- 可以通过常规Spring Bean的方式注册。
- 可以构建一个外部对象通过
ConfigurableApplicationContext#addApplicationListener
注册到应用上下文中。 - 另外,从Spring4.2后开始支持以注解的方式注册。
并且监听器的回调方法执行有同步和异步两种方式。下面将展示具体的代码实现。
2.1 通过注册Spring Bean的方式注册监听器
- 首先定义一个监听器实现类MyApplicationListener,并监听
org.springframework.context.event.ContextRefreshedEvent
事件。后面的代码实现基本都是继续沿用这个实现类
public class MyApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println ("MyApplicationListener#onApplicationEvent event:" + event);
}
}
-
编写Main方法启动Spring容器并注册监听器
public class SpringEventDemo { public static void main(String[] args) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext (); //注册监听器 applicationContext.register (MyApplicationListener.class); //启动Spring应用上下文 applicationContext.refresh (); //关闭Spring应用上下文 applicationContext.close(); } }
这里Main方法执行后这个console会打印
MyApplicationListener#onApplicationEvent event:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@49c2faae, started on Mon Apr 26 14:17:44 CST 2021]
2.2 通过ConfigurableApplicationContext#addApplicationListener注册监听器
- 编写Main方法启动Spring容器并注册监听器
public class SpringEventDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext ();
//实例化监听器对象
MyApplicationListener myApplicationListener = new MyApplicationListener ();
//将监听器对象添加到Spring应用上下文
applicationContext.addApplicationListener (myApplicationListener);
//启动Spring应用上下文
applicationContext.refresh ();
//关闭Spring应用上下文
applicationContext.close();
}
}
- 这里Main方法执行后这个console会打印
MyApplicationListener#onApplicationEvent event:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@49c2faae, started on Mon Apr 26 14:17:44 CST 2021]
2.3通过注解方式注册&设置监听器异步回调
- 通过@EventListener标记在方法上设置监听器回调方法
- 通过@Async和@EnableAsync开启异步执行
- 开启异步回调监听器除了通过注解的方式,还能获取ApplicationContext的SimpleApplicationEventMulticaster,调用
SimpleApplicationEventMulticaster#setTaskExecutor
设置线程池开启异步执行。但这种方式要注意我们需要自己通过代码进行线程池关闭,Spring应用上下文不会关闭我们设置进去的线程池。
- 开启异步回调监听器除了通过注解的方式,还能获取ApplicationContext的SimpleApplicationEventMulticaster,调用
@EnableAsync
public class AnnotatedAsyncEventListenerDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext ();
applicationContext.register (AnnotatedAsyncEventListenerDemo.class);
applicationContext.refresh ();
applicationContext.publishEvent (new MyApplicationEvent ("AnnotatedAsyncEventListenerDemo#MyApplicationEvent"));
applicationContext.close ();
}
@Async
@EventListener
public void onListenerEvent(ContextRefreshedEvent contextRefreshedEvent) {
System.out.printf ("事件执行当前线程:%s,事件为:%s\n",Thread.currentThread ().getName (),contextRefreshedEvent);
}
}
- Main方法执行后这个console会打印,这里可以看到处理事件的线程并不是main线程
事件执行当前线程:SimpleAsyncTaskExecutor-1,事件为:org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@5d099f62, started on Mon Apr 26 17:04:34 CST 2021]
2.4 自定义事件
尝试自定义一个事件并进行发布。
- 定义自定义事件MyApplicationEvent
public class MyApplicationEvent extends ApplicationEvent {
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public MyApplicationEvent(String source) {
super (source);
}
@Override
public String getSource() {
return (String) super.getSource ();
}
}
- 定义监听自定义事件的监听器MyEventListener
public class MyEventListener implements ApplicationListener<MyApplicationEvent> { @Override public void onApplicationEvent(MyApplicationEvent event) { System.out.printf ("执行事件:[%s]\n",event); } }
- Main方法编写测试,这里通过
org.springframework.context.ApplicationEventPublisherAware
回调接口发布事件
public class CustomizedEventDemo implements ApplicationEventPublisherAware {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext ();
applicationContext.register (CustomizedEventDemo.class);
applicationContext.addApplicationListener (new MyEventListener ());
applicationContext.refresh ();
applicationContext.close ();
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
applicationEventPublisher.publishEvent (new MyApplicationEvent ("hello,setApplicationEventPublisher"));
}
}
2.5 事件异常处理器
在Spring 3.0之后提供了一个org.springframework.util.ErrorHandler
能够处理事件异常的情况,如图2-5-1。下面将展示如何向Spring应用上下文中注册事件异常处理器
public class ErrorHandlerDemo {
public static void main(String[] args) {
GenericApplicationContext applicationContext = new GenericApplicationContext ();
applicationContext.addApplicationListener (new MyEventListener ());
applicationContext.refresh ();
ApplicationEventMulticaster applicationEventMulticaster = applicationContext.getBean (AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (applicationEventMulticaster instanceof SimpleApplicationEventMulticaster) {
SimpleApplicationEventMulticaster simpleApplicationEventMulticaster =
(SimpleApplicationEventMulticaster) applicationEventMulticaster;
/**
* Spring事件错误处理代码
* SimpleApplicationEventMulticaster中设置ErrorHandler
* {@link org.springframework.util.ErrorHandler}
*/
simpleApplicationEventMulticaster.setErrorHandler (new ErrorHandler () {
@Override
public void handleError(Throwable t) {
System.err.println ("ErrorHandler处理事件错误," + t);
}
});
/**
设置一个主动抛出异常的监听器
*/
simpleApplicationEventMulticaster.addApplicationListener (new ApplicationListener<MyApplicationEvent> () {
@Override
public void onApplicationEvent(MyApplicationEvent event) {
throw new RuntimeException ("主动抛出异常,测试事件错误处理");
}
});
}
// 发布MyApplicationEvent事件触发异步事件
applicationEventMulticaster.multicastEvent (new MyApplicationEvent ("AsyncEventListenerDemo"));
System.out.println ("关闭Spring应用上下文");
applicationContext.close ();
}
}
- 这里Main方法会输出
ErrorHandler处理事件错误,java.lang.RuntimeException: 主动抛出异常,测试事件错误处理
以上就是Spring事件相关的操作,此外如果我们需要控制监听器的调用顺序,我们可以通过@Order控制具体的执行顺序。
3.Spring Event源码分析
3.1 容器启动时,事件的相关源码
这里从org.springframework.context.support.AbstractApplicationContext#refresh
方法开始入手。首先refresh方法调用的prepareRefresh
方法是容器启动前的准备,这里的相关操作是初始化一个名为earlyApplicationEvents
的Set<ApplicationEvent>
,这是用于存放早期事件的一个容器,这个容器的出现了是为了修复Spring之前的一个缺陷,这里切换到Spring Framework 3.0的源码并尝试分析这个缺陷是什么以及到底如何出现的。
这里首先提前说一下,Spring应用上下文拥有事件发布的能力,该能力基于其成员属性org.springframework.context.event.ApplicationEventMulticaster
这个接口的实现类实现的
- 首先先看一下refresh方法的整体流程,如图3-1-1所示
- 首先会做启动前准备prepareBeanFactory
- 然后调用invokeBeanFactoryPostProcessor回调
org.springframework.beans.factory.config.BeanFactoryPostProcessor
的方法 - 再望后执行initApplicationEventMulticaster,这个方法是实例化
org.springframework.context.event.ApplicationEventMulticaster
- 最后调用registerListeners,将用户的监听器注册到容器中
-
进入
AbstractApplicationContext#prepareRefresh
,当时并没有初始化早期事件的容器,如图3-1-2
导致缺陷发生的场景如下,假设创建一个类是实现了 BeanFactoryPostProcessor以及ApplicationContextAware接口的。ApplicationContext是有发布事件的能力,其能力依赖于ApplicationEventMulticaster。当我们在通过ApplicationContextAware回调获取ApplicaitonContext实例后,然后在BeanFactoryPostProcessor的回调接口中利用返回的ApplicationContext实例发布实现,Spring应用上下文就会抛出异常,因为从上面的调用顺序来看,BeanFactoryPostProcessor的回调时间早于ApplicationEventMulticaster的实例化,这里的ApplicationEventMulticaster还是null的所以会抛出NPE。下面展示一下异常的示例
定义一个调用类BugInvoker
public class BugInvoker implements BeanFactoryPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
applicationContext.publishEvent (new MyApplicationEvent ("hello world"));
}
}
- 编写Main方法测试
public class Spring3BugDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext ();
//注册到容器中
applicationContext.register (BugInvoker.class);
applicationContext.addApplicationListener (new ApplicationListener () {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println (event);
}
});
applicationContext.refresh ();
applicationContext.close ();
}
}
- 这里是启动后抛出的具体异常信息,异常信息告诉用户ApplicationEventMulticaster还没有实例化完成
Exception in thread "main" java.lang.IllegalStateException: ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: org.springframework.context.annotation.AnnotationConfigApplicationContext@378fd1ac: startup date [Tue Apr 27 14:46:22 CST 2021]; root of context hierarchy
at org.springframework.context.support.AbstractApplicationContext.getApplicationEventMulticaster(AbstractApplicationContext.java:307)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:294)
at com.kgyam.event.springEvent.Spring3BugDemo$BugInvoke.postProcessBeanFactory(Spring3BugDemo.java:48)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:624)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:614)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:398)
at com.kgyam.event.springEvent.Spring3BugDemo.main(Spring3BugDemo.java:31)
- 而3.0之后加入的早期事件容器就是为了修复这个缺陷,对早于ApplicationEventMulticaster实例化前发布的事件先缓存在这个容器中,等待ApplicationEventMulticaster实例化完成后再对这个容器里的事件进行发布。
3.2 监听器注册
监听器的注册比较简单,通过org.springframework.context.support.AbstractApplicationContext#addApplicationListener
作为入口
-
将监听器添加到ApplicationEventMulticaster中,如图3-2-1
-
org.springframework.context.event.AbstractApplicationEventMulticaster#addApplicationListener
中的添加监听器逻辑是排除掉重复的监听器以免发生二次调用,然后就会把这个监听器对象添加到监听器列表中并清理监听器缓存Map,如图3-2-2
到这里就基本完成了添加监听器的过程了,接下来看下事件发布相关源码。
3.3事件发布
事件发布通过org.springframework.context.support.AbstractApplicationContext#publishEvent
作为入口
-
首先根据发布的事件对象,如果是非ApplicationEvent类型的就会将其转换为PayloadApplicationEvent对象,如图3-3-1
- 然后就是如果earlyApplicationEvents不为null就会将其存放到早期事件容器中,这里是表示applicationEventMulticaster对象没有初始化完成,将事件缓存到该早期事件容器中。在applicationEventMulticaster初始化完成并发布所有早期事件容器里面的事件后,会将该容器设置为null。
- 如果applicationEventMulticaster应用事件广播器已经实例化,那么就会调用
org.springframework.context.event.ApplicationEventMulticaster#multicastEvent
发布事件,如图3-3-2
- 往下进入ApplicationEventMulticaster的实现类
org.springframework.context.event.SimpleApplicationEventMulticaster
看下具体的事件发布逻辑,如图3-3-3
- 调用getApplicationListeners方法获取对应事件类型对应的监听器列表,这其中的逻辑相对是比较多的。
- 首先会根据事件类型和事件源类型生成对应的监听器缓存key对象
org.springframework.context.event.AbstractApplicationEventMulticaster.ListenerCacheKey
,根据这个key从retrieverCache(Map对象,如图3-3-4)获取监听器列表对象org.springframework.context.event.AbstractApplicationEventMulticaster.ListenerRetriever
,这个ListenerRetriever是存放了事件类型相关的监听器列表, -
如果没有从缓存中获取到对应的监听器列表对象,就从defaultRetriever(存放所有监听器的对象,图3-3-5)中找到匹配的监听器生成对应的ListenerRetriever并重新缓存到retrieverCache中,最后返回监听器列表。
-
返回监听器列表后就通过invokeListener遍历回调监听器,如图3-3-6。这里try-catch将捕获监听器抛出的异常并交给ErrorHandler处理,如图3-3-7
-
到这里事件发布流程基本就结束了。但还有一个地方需要注意,我们回到
org.springframework.context.event.ApplicationEventMulticaster#multicastEvent
这里,如果应用上下文存在父子关系的话,该事件就会传递到父的应用上下文中,这种情况下注意出现事件被多次处理的情况,如图3-3-8。
3.4 @EventListener注册过程
在org.springframework.context.event.EventListener
的注释中有提及到该注解是通过一个内建bean对象org.springframework.context.event.EventListenerMethodProcessor
进行处理的,如图3-4-1所示。
-
首先先看下EventListenerMethodProcessor类,如图3-4-2。对于处理EventListener注解的核心方法是在afterSingletonsInstantiated方法(实例化完成后处理接口
org.springframework.beans.factory.SmartInitializingSingleton
的接口方法)。将断点打在这个方法上作为入口看下对应的处理逻辑。
该方法首先会获取容器中所有的beanName并遍历,然后根据beanName在容器中获取其对应的Class类型。
-
获取到Class类型后找到所有标记了注解@EventListener的方法并放到一个名为annotatedMethods的Map中,如图3-4-3。
-
如果annotatedMethods非空,就遍历这个Map并通过
org.springframework.context.event.EventListenerFactory
的实现类对其进行处理,如图3-4-5。这里会针对每个标记了注解的Method对象创建一个对应的事件监听适配器并通过应用上下文的addApplicationContext方法将其添加进去。
-
EventListenerFactory在Spring的内建实现只有一个
org.springframework.context.event.DefaultEventListenerFactory
,如图3-4-6。该接口主要有两个方法,第一个是判断是否支持该Method对象,DefaultEventListenerFactory默认返回true。第二个方法是针对该方法创建一个事件监听器,DefaultEventListenerFactory会创建一个事件监听适配器org.springframework.context.event.ApplicationListenerMethodAdapter#ApplicationListenerMethodAdapter
。
@EventListener注解的处理基本到这里就结束了。
4.总结
Spring Event使用的是典型的观察者模型,这种情况能够让后监听器更加符合单一职责原则,并且能够提高系统的扩展性。同时我们还知道了Spring应用上下文ApplicationContext是拥有事件发布的能力的,其能力是依赖于org.springframework.context.event.ApplicationEventMulticaster
。
最后还需要注意的是,如果使用Spring 3.0这种早期版本需要注意事件发布必须晚于事件广播器实例化。