在业务系统中利用事件机制十分常见。那么,在spring中如何发布一个事件,又如何监听并处理一个事件呢?
事件源通常是某个业务类,当它处理完自身的业务逻辑后,向外某个事件发布事件。

  1. public void produceData{
  2. springContextHolder.publishEvent(new DataEvent("数据产生了"))
  3. }

事件源中发布的事件DateEvent是一个继承了applicationEvent的自定义类

  1. public class DataEvent extends ApplicationEvent{
  2. String field1;
  3. public DataEvent(String msg){
  4. super("DataEvent");
  5. this.field1=msg;
  6. }
  7. ....
  8. }

DataEventListener是一个实现了ApplicationListener的类,通过重写onApplicationEvent方法,可以实现监听器接收到相应事件后的处理逻辑。

  1. public class DataEventListener implements ApplicationListener<DataEvent>
  2. {
  3. @Override
  4. public void onApplicationEvent(DataEvent dataEvent)
  5. {
  6. //这里可以写自己的逻辑
  7. System.out.print(dataEvent.getField1());
  8. }
  9. }

一、springContextHolder

从上边可以看出,springContextHolder.publishEvent向外推送了数据。springContextHolder是自己定义的类,它实现了ApplicationContextAware接口。

  1. public class SpringContextHolder implements ApplicationContextAware {
  2. private ApplicationContext applicationContext;
  3. @Override
  4. public void setApplicationContext(ApplicationContext applicationContext)throws BeansException {
  5. this.applicationContext=applicationContext;
  6. }
  7. public ApplicationContext getApplicationContext(){
  8. return applicationContext;
  9. }
  10. /**
  11. * 发布事件
  12. * @param applicationEvent
  13. */
  14. public void publishEvent(ApplicationEvent applicationEvent){
  15. applicationContext.publishEvent(applicationEvent);
  16. }
  17. }

我们可以得出两点:

  1. springContextHolder继承了ApplicationContextAware接口。
  2. springContextHolder也不是真正发送事件的对象,而是委托了applicationContext发布事件。


    二、ApplicationContextAware接口

    Spring提供了大量的aware接口,spring的aware接口赋予bean获得spring容器服务的能力。
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实例生命周期的执行过程:

  1. Spring对bean进行实例化,默认bean是单例;
  2. Spring对bean进行依赖注入;
  3. 如果bean实现了BeanNameAware接口,spring将bean的id传给setBeanName()方法;
  4. 如果bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将BeanFactory实例传进来;
  5. 如果bean实现了ApplicationContextAware接口,它的setApplicationContext()方法将被调用,将应用上下文的引用传入到bean中;
  6. 如果bean实现了BeanPostProcessor接口,它的postProcessBeforeInitialization方法将被调用;
  7. 如果bean实现了InitializingBean接口,spring将调用它的afterPropertiesSet接口方法,类似的如果bean使用了init-method属性声明了初始化方法,该方法也会被调用;
  8. 如果bean实现了BeanPostProcessor接口,它的postProcessAfterInitialization接口方法将被调用;( 此时bean已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用上下文被销毁;)
  9. 若bean实现了DisposableBean接口,spring将调用它的distroy()接口方法。同样的,如果bean使用了destroy-method属性声明了销毁方法,则该方法被调用;

到这里就解答了”springContextHolder中的applicationContext是什么时候被初始化的?”的问题。
SpringContextHolder拿到applicationContext后,调用applicationContext的publishEvent方法发布事件。

三、ApplicationContext发布事件的原理

打开ApplicationContex的源码,我们发现其内部并没有publishEvent方法。经过一番查找,发现Application的publishEvent方法继承自ApplicationEventPublisher。

  1. @FunctionalInterface
  2. public interface ApplicationEventPublisher {
  3. /*这里定义了default方法,关于default的问题可以看我的另外一篇博文*/
  4. default void publishEvent(ApplicationEvent event) {
  5. publishEvent((Object) event);
  6. }
  7. /*这里定义了publishEvent的接口*/
  8. void publishEvent(Object event);
  9. }

ApplicationEventPublisher的具体实现在抽象类AbstractApplicationContext中,为了方便起见,下文将这个称为“抽象上下文“。下边是AbstractApplicationContext对publishEvent的具体实现。

  1. protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
  2. // 将事件装饰成ApplicationEvent
  3. ApplicationEvent applicationEvent;
  4. if (event instanceof ApplicationEvent) {
  5. applicationEvent = (ApplicationEvent) event;
  6. }
  7. else {
  8. applicationEvent = new PayloadApplicationEvent<>(this, event);
  9. if (eventType == null) {
  10. eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
  11. }
  12. }
  13. // 广播事件
  14. if (this.earlyApplicationEvents != null) {
  15. this.earlyApplicationEvents.add(applicationEvent);
  16. }
  17. else {
  18. getApplicationEventMulticaster().multicastEvent(applicationEvent,eventType);
  19. }
  20. // 如果有父容器,则在父容器内也进行广播
  21. if (this.parent != null) {
  22. if (this.parent instanceof AbstractApplicationContext) {
  23. ((AbstractApplicationContext) this.parent).publishEvent(event,eventType);
  24. }
  25. else {
  26. this.parent.publishEvent(event);
  27. }
  28. }
  29. }

可以看出,AbstractApplicationContext中对publishEvent的实现一共分为以下几个步骤。

  1. 首先将传入的事件装饰成ApplicationEvent,如果本身已经是ApplicationEvent,则无需处理。直接到第二步。
  2. 这里分为两种情况:
    1. 如果earlyApplicationEvents不为空,那么将当前事件加入earlyApplicationEvents,第二步结束。(下文会说earlyApplicationEvents是什么东西)
    2. 如果earlyApplicationEvents为空,则通过getApplicationEventMulticaster拿到事件广播器,然后将事件广播出去。(这里后文也会详细交代)
  3. 如果有父容器,比如springMVC有父容器spring等。那么要再父容器内也将此事件进行广播。


    四、事件广播

    这里需要重点说一下广播事件这个部分 ```java // 大部分时间会走else分支(看下文解释) if (this.earlyApplicationEvents != null) { this.earlyApplicationEvents.add(applicationEvent); } else { getApplicationEventMulticaster().multicastEvent(applicationEvent,eventType); }
  1. `earlyApplicationEvents``AbstractApplicationContext`中定义的一个字段,代码如下所示。
  2. ```java
  3. @Nullable
  4. private Set<ApplicationEvent> earlyApplicationEvents;

这里可以看出两点:

  1. earlyApplicationEvents是一个set
  2. @Nullale说明它可以为空

通过查阅代码,知道earlyApplicationEvents会在容器准备启动时进行初始化,具体的初始化过程如下:

  1. protected void prepareRefresh() {
  2. //省略无关代码
  3. ....
  4. //对earlyApplicationEvents进行初始化
  5. this.earlyApplicationEvents = new LinkedHashSet<>();
  6. }

earlyApplicationEvents用来存放容器启动后需要发布的事件。它会在容器启动的prepareRefresh环节初始化为一个LinkedHashSet

即放在earlyApplicationEvents事件不会立刻发布,而是在容器启动的某一个环节进行发布。在哪一个环节?

  1. protected void registerListeners() {
  2. // 省略无关代码
  3. ....
  4. // 发布earlyApplicationEvents中的时间,并且让earlyApplicationEvents为空
  5. Set<ApplicationEvent> earlyEventsToProcess =this.earlyApplicationEvents;
  6. this.earlyApplicationEvents = null;
  7. if (earlyEventsToProcess != null) {
  8. for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
  9. getApplicationEventMulticaster().multicastEvent(earlyEvent);
  10. }
  11. }
  12. }

earlyApplicationEvents会在容器启动的registerListeners环节进行发布。并且在预置事件发布后,earlyApplicationEvents会被销毁(this.earlyApplicationEvents = null;)

总结一下earlyApplicationEvents:

  1. 它是一个set,用来存放一些容器启动时需要发布的事件。在earlyApplicationEvents中的事件被发布、容器彻底启动后,它将被置空
  2. 再回到这一小节最初,我们看到当容器彻底启动后if (this.earlyApplicationEvents != null)这个判断肯定是false。即我们自定义事件的发布会走getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType)。

五、这里说一下什么是早期事件?什么是早期的事件监听器呢?

当使用spring上下文发布事件的时候,如下代码:

  1. applicationContext.publishEvent(事件对象)

其内部最终会用到AbstractApplicationContext下面这个属性来发布事件,但是可能此时applicationEventMulticaster还没有创建好,是空对象,所以此时是无法广播事件的,那么此时发布的事件就是早期的事件。就会被放在this.earlyApplicationEvents中暂时存放着,当时间广播器applicationEventMulticaster创建好了之后,才会会将早期的事件广播出去。

同理,当applicationEventMulticaster还未准备好的时候,调用下面下面代码向spring上下文中添加事件监听器的时候,这时放进去的监听器就是早期的事件监听器。

  1. applicationContext.addApplicationListener(事件监听器对象)

说了这么多,理解起来很简单,就是当事件广播器applicationEventMulticaster还未准备好的时候,此时向上下文中添加的事件就是早期的事件,会被放到this.earlyApplicationEvents中,此时这个事件暂时没办法广播。

从一个简单的例子开始分析源码

定义一个MyConfigApplicationContext继承与AnnotationConfigApplicationContext。这个类复写了Spring预留的接口onRefresh()方法,并且发布了一个自定义的事件,这个事件会注册到this.earlyApplicationEvents中。容器在registerListeners()方法之前publishEvent的都是早期事件,所以我们重写了onRefresh()方法,并在其中发布了一个事件,该事件为早期事件,然后在registerListeners时,被广播器广播到监听器。这块可以详细的看看这篇文章:https://www.jianshu.com/p/af987a17a9d8

  1. public class MyConfigApplicationContext extends AnnotationConfigApplicationContext {
  2. public MyConfigApplicationContext(Class c) {
  3. super(c);
  4. }
  5. @Override
  6. protected void onRefresh() throws BeansException {
  7. this.publishEvent(new ApplicationEvent("我手动发布了一个事件") {
  8. @Override
  9. public Object getSource() {
  10. return super.getSource();
  11. }
  12. });
  13. super.onRefresh();
  14. }
  15. }

我们先不讨论为何要这样写,继续写配置类

  1. @Configuration
  2. @ComponentScan(basePackages = {"com.example.demo"})
  3. public class MainConfig {
  4. }

然后主类

  1. public class DemoApplication {
  2. public static void main(String[] args) {
  3. MyConfigApplicationContext ctx = new MyConfigApplicationContext(MainConfig.class);
  4. }
  5. }

运行一下

运行结果