在业务系统中利用事件机制十分常见。那么,在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>{@Overridepublic void onApplicationEvent(DataEvent dataEvent){//这里可以写自己的逻辑System.out.print(dataEvent.getField1());}}
一、springContextHolder
从上边可以看出,springContextHolder.publishEvent向外推送了数据。springContextHolder是自己定义的类,它实现了ApplicationContextAware接口。
public class SpringContextHolder implements ApplicationContextAware {private ApplicationContext applicationContext;@Overridepublic 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。
@FunctionalInterfacepublic interface ApplicationEventPublisher {/*这里定义了default方法,关于default的问题可以看我的另外一篇博文*/default void publishEvent(ApplicationEvent event) {publishEvent((Object) event);}/*这里定义了publishEvent的接口*/void publishEvent(Object event);}
ApplicationEventPublisher的具体实现在抽象类AbstractApplicationContext中,为了方便起见,下文将这个称为“抽象上下文“。下边是AbstractApplicationContext对publishEvent的具体实现。
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {// 将事件装饰成ApplicationEventApplicationEvent 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@Nullableprivate 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);}@Overrideprotected void onRefresh() throws BeansException {this.publishEvent(new ApplicationEvent("我手动发布了一个事件") {@Overridepublic 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);}}
运行一下
运行结果
