在业务系统中利用事件机制十分常见。那么,在spring中如何发布一个事件,又如何监听并处理一个事件呢?
事件源通常是某个业务类,当它处理完自身的业务逻辑后,向外某个事件发布事件。
public void produceData{
springContextHolder.publishEvent(new DataEvent("数据产生了"))
}
事件源中发布的事件DateEvent是一个继承了applicationEvent的自定义类
public class DataEvent extends ApplicationEvent{
String field1;
public DataEvent(String msg){
super("DataEvent");
this.field1=msg;
}
....
}
DataEventListener是一个实现了ApplicationListener的类,通过重写onApplicationEvent方法,可以实现监听器接收到相应事件后的处理逻辑。
public class DataEventListener implements ApplicationListener<DataEvent>
{
@Override
public void onApplicationEvent(DataEvent dataEvent)
{
//这里可以写自己的逻辑
System.out.print(dataEvent.getField1());
}
}
一、springContextHolder
从上边可以看出,springContextHolder.publishEvent向外推送了数据。springContextHolder是自己定义的类,它实现了ApplicationContextAware接口。
public class SpringContextHolder implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext)throws BeansException {
this.applicationContext=applicationContext;
}
public ApplicationContext getApplicationContext(){
return applicationContext;
}
/**
* 发布事件
* @param applicationEvent
*/
public void publishEvent(ApplicationEvent applicationEvent){
applicationContext.publishEvent(applicationEvent);
}
}
我们可以得出两点:
- springContextHolder继承了ApplicationContextAware接口。
- springContextHolder也不是真正发送事件的对象,而是委托了applicationContext发布事件。
Spring提供了大量的aware接口,spring的aware接口赋予bean获得spring容器服务的能力。
二、ApplicationContextAware接口
aware接口 | 作用 |
---|---|
BeanNameAware | 可以获取容器中bean的名称 |
BeanFactoryAware | 获取当前bean factory这也可以调用容器的服务 |
MessageSourceAware | 获得message source,这也可以获得文本信息 |
ResourceLoaderAware | 获得资源加载器,可以获得外部资源文件的内容 |
applicationEventPulisherAware | 应用事件发布器,可以发布事件 |
ApplicationContextAware | 这也可以调用容器的服务 |
如果一个bean想要在程序中获取spring管理的bean,常见做法是:new ClassPathXmlApplication("application.xml").getBean("xxx")
,这样相当于重新启动一次spring容器,效率不高。
ApplicationContextAware是spring提供的众多接口中的一个。当一个bean实现ApplicationContextAware接口时,必须重写它的setApplicationContext方法。
当bean的实例化时,会设置对象属性,检查aware相关接口并设置依赖。此时就是aware被调用的时候。spring容器会自动调用ApplicationContextAware实现类中的setApplicationContext方法。因此,bean通过setApplicationContext方法可以获得容器的context。下边是bean实例生命周期的执行过程:
- Spring对bean进行实例化,默认bean是单例;
- Spring对bean进行依赖注入;
- 如果bean实现了BeanNameAware接口,spring将bean的id传给setBeanName()方法;
- 如果bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将BeanFactory实例传进来;
- 如果bean实现了ApplicationContextAware接口,它的setApplicationContext()方法将被调用,将应用上下文的引用传入到bean中;
- 如果bean实现了BeanPostProcessor接口,它的postProcessBeforeInitialization方法将被调用;
- 如果bean实现了InitializingBean接口,spring将调用它的afterPropertiesSet接口方法,类似的如果bean使用了init-method属性声明了初始化方法,该方法也会被调用;
- 如果bean实现了BeanPostProcessor接口,它的postProcessAfterInitialization接口方法将被调用;( 此时bean已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用上下文被销毁;)
- 若bean实现了DisposableBean接口,spring将调用它的distroy()接口方法。同样的,如果bean使用了destroy-method属性声明了销毁方法,则该方法被调用;
到这里就解答了”springContextHolder中的applicationContext是什么时候被初始化的?”的问题。
SpringContextHolder拿到applicationContext后,调用applicationContext的publishEvent方法发布事件。
三、ApplicationContext发布事件的原理
打开ApplicationContex的源码,我们发现其内部并没有publishEvent方法。经过一番查找,发现Application的publishEvent方法继承自ApplicationEventPublisher。
@FunctionalInterface
public interface ApplicationEventPublisher {
/*这里定义了default方法,关于default的问题可以看我的另外一篇博文*/
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
/*这里定义了publishEvent的接口*/
void publishEvent(Object event);
}
ApplicationEventPublisher的具体实现在抽象类AbstractApplicationContext中,为了方便起见,下文将这个称为“抽象上下文“。下边是AbstractApplicationContex
t对publishEvent的具体实现。
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
// 将事件装饰成ApplicationEvent
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
}
}
// 广播事件
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
getApplicationEventMulticaster().multicastEvent(applicationEvent,eventType);
}
// 如果有父容器,则在父容器内也进行广播
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event,eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
可以看出,AbstractApplicationContext
中对publishEvent
的实现一共分为以下几个步骤。
- 首先将传入的事件装饰成
ApplicationEvent
,如果本身已经是ApplicationEvent
,则无需处理。直接到第二步。 - 这里分为两种情况:
- 如果
earlyApplicationEvents
不为空,那么将当前事件加入earlyApplicationEvents
,第二步结束。(下文会说earlyApplicationEvents
是什么东西) - 如果
earlyApplicationEvents
为空,则通过getApplicationEventMulticaster
拿到事件广播器,然后将事件广播出去。(这里后文也会详细交代)
- 如果
- 如果有父容器,比如
springMVC有父容器spring
等。那么要再父容器内也将此事件进行广播。
这里需要重点说一下广播事件这个部分 ```java // 大部分时间会走else分支(看下文解释) if (this.earlyApplicationEvents != null) { this.earlyApplicationEvents.add(applicationEvent); } else { getApplicationEventMulticaster().multicastEvent(applicationEvent,eventType); }
四、事件广播
`earlyApplicationEvents`是`AbstractApplicationContext`中定义的一个字段,代码如下所示。
```java
@Nullable
private Set<ApplicationEvent> earlyApplicationEvents;
这里可以看出两点:
- earlyApplicationEvents是一个set
- @Nullale说明它可以为空
通过查阅代码,知道earlyApplicationEvents
会在容器准备启动时进行初始化,具体的初始化过程如下:
protected void prepareRefresh() {
//省略无关代码
....
//对earlyApplicationEvents进行初始化
this.earlyApplicationEvents = new LinkedHashSet<>();
}
earlyApplicationEvents
用来存放容器启动后需要发布的事件。它会在容器启动的prepareRefresh环节初始化为一个LinkedHashSet
。
即放在earlyApplicationEvents事件不会立刻发布,而是在容器启动的某一个环节进行发布。在哪一个环节?
protected void registerListeners() {
// 省略无关代码
....
// 发布earlyApplicationEvents中的时间,并且让earlyApplicationEvents为空
Set<ApplicationEvent> earlyEventsToProcess =this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (earlyEventsToProcess != null) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}
earlyApplicationEvents会在容器启动的registerListeners环节进行发布。并且在预置事件发布后,earlyApplicationEvents会被销毁(this.earlyApplicationEvents = null;)
总结一下earlyApplicationEvents:
- 它是一个set,用来存放一些容器启动时需要发布的事件。在earlyApplicationEvents中的事件被发布、容器彻底启动后,它将被置空。
- 再回到这一小节最初,我们看到当容器彻底启动后
if (this.earlyApplicationEvents != null)
这个判断肯定是false。即我们自定义事件的发布会走getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType)。
五、这里说一下什么是早期事件?什么是早期的事件监听器呢?
当使用spring上下文发布事件的时候,如下代码:
applicationContext.publishEvent(事件对象)
其内部最终会用到AbstractApplicationContext
下面这个属性来发布事件,但是可能此时applicationEventMulticaster
还没有创建好,是空对象,所以此时是无法广播事件的,那么此时发布的事件就是早期的事件。就会被放在this.earlyApplicationEvents
中暂时存放着,当时间广播器applicationEventMulticaster
创建好了之后,才会会将早期的事件广播出去。
同理,当applicationEventMulticaster还未准备好的时候,调用下面下面代码向spring上下文中添加事件监听器的时候,这时放进去的监听器就是早期的事件监听器。
applicationContext.addApplicationListener(事件监听器对象)
说了这么多,理解起来很简单,就是当事件广播器applicationEventMulticaster还未准备好的时候,此时向上下文中添加的事件就是早期的事件,会被放到this.earlyApplicationEvents中,此时这个事件暂时没办法广播。
从一个简单的例子开始分析源码
定义一个MyConfigApplicationContext继承与AnnotationConfigApplicationContext。这个类复写了Spring
预留的接口onRefresh()
方法,并且发布了一个自定义的事件,这个事件会注册到this.earlyApplicationEvents
中。容器在registerListeners()
方法之前publishEvent的都是早期事件,所以我们重写了onRefresh()
方法,并在其中发布了一个事件,该事件为早期事件,然后在registerListeners
时,被广播器广播到监听器。这块可以详细的看看这篇文章:https://www.jianshu.com/p/af987a17a9d8
public class MyConfigApplicationContext extends AnnotationConfigApplicationContext {
public MyConfigApplicationContext(Class c) {
super(c);
}
@Override
protected void onRefresh() throws BeansException {
this.publishEvent(new ApplicationEvent("我手动发布了一个事件") {
@Override
public Object getSource() {
return super.getSource();
}
});
super.onRefresh();
}
}
我们先不讨论为何要这样写,继续写配置类
@Configuration
@ComponentScan(basePackages = {"com.example.demo"})
public class MainConfig {
}
然后主类
public class DemoApplication {
public static void main(String[] args) {
MyConfigApplicationContext ctx = new MyConfigApplicationContext(MainConfig.class);
}
}
运行一下
运行结果