阅读 763

Spring框架中事件发布及其原理

在业务系统中,当多个模块之间需要进行通信的时候,事件可以作为信使传递消息,从而使模块之间免于耦合,因此利用事件机制十分常见。那么,在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;
  }
  	....
}
复制代码

定义监听器

事件、发布事件的事件源都定义好了,那么由谁来发出事件呢?DataEvent事件的监听器DataEventListener是一个实现了ApplicationListener的类,通过重写onApplicationEvent方法,可以实现监听器接收到相应事件后的处理逻辑。

为了简单起见,这里就直接输出dataEvent事件中携带的msg。

public class DataEventListener implements ApplicationListener<DataEvent>
{
   @Override
    public void onApplicationEvent(DataEvent dataEvent)
    {
     	//这里可以写自己的逻辑
      System.out.print(dataEvent.getField1());
    }
}
复制代码

测试

//业务类执行produceData方法
produceData();
//控制台会输出
"数据产生了"
复制代码

上述是一个spring发布事件的例子。

作为程序员不能只会用,不懂其中的原理。这样长久下去会让自己在职场上失去议价能力,在互联网行业是很危险的。

说个题外话吧,我记得之前有段时间公司的业务比较忙,于是就把一部分非核心工作交给了外包公司的人,外包的人员在我们公司驻场开发。所以和他们之间有一段比较密切的接触

一开始不太熟悉的时候,我内心替他们感到不公,觉得都是同行,都一样上下班,为啥他们待遇那么低(那时候年轻,圣母心泛滥)。但是时间一久,我就发现问题所在了。他们基本上有一个很普遍的问题,就是不求甚解。当他们拿到一个需求后,不会多问,不会深究。基本就是照着模板代码替换和修改,称之为“像素级”的模仿都不为过

由于不清楚其中的原理,所以经常会被一些小问题给卡住。不知道为什么,总是感觉他们很焦虑,被卡住就直接copy异常提示赶紧上百度搜,甚至不会自己去加工和提炼一下异常提示再去搜。大多数时候,逻辑问题是搜不出来的,然后他们会变得越来越焦躁,压力越来越大,最后查不到就束手无策

久而久之,个人自信心下降,更加没有心情和决心去探究一个问题。最后是啥结局,可能不用多说,我们都明白

不过凡事都有例外,当时那一批驻场员工中有一个小伙年龄不大,小时候在农村教育条件不好,后来上了个大专。毕业以后大公司卡学历,最后没办法才去的外包。但是,他和别人不太一样。他的决心和求知欲都明显超过那一批中的其他人。他遇到问题,也不会,有时候也焦虑,他也上网查。但是和别人不一样。他查过的东西,绝对不会再卡住他第二次。甚至查过一个问题,相似的问题也难不住他。他人并不聪明,就是有一股往里钻的劲儿支撑着他

我对他印象特别深刻,当时就觉得他和其他人那种应付交差的态度很不一样。果然,后来听同事说这个小伙有次去另外一个一线大厂驻场,被那个组的leader看中,最后留下来变成正式员工了

顺便给自己的公粽号打个广告吧,欢迎围观“趣论编程”。

好了,说了太多题外话。说回正题,我们探究一下spring为什么能够发布事件吧,探究一下它背后的机理是啥。

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发布事件。

那么我们分别来讨论下边两点。

1.springContextHolder为什么能拿到applicationContext,是什么时候被初始化的?

2.委托给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实例生命周期的执行过程:

  • 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发布事件的原理

我们已经知道,通过applicationContext可以发布事件,那么为什么applicationContext能够发布事件呢?它的原理又是什么呢?

首先看一下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);
}
复制代码

AbstractApplicationContext

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

下边是AbstractApplicationContext对publishEvent的具体实现。

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
		Assert.notNull(event, "Event must not be null");
		if (logger.isTraceEnabled()) {
			logger.trace("Publishing event in "+getDisplayName()+": "+event);
		}

		// 将事件装饰成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,则无需处理。直接到第二步。

二、这里分为两种情况:

​ 1.如果earlyApplicationEvents不为空,那么将当前事件加入earlyApplicationEvents,第二步结束。(下文会说earlyApplicationEvents是什么东西)

​ 2.如果earlyApplicationEvents为空,则通过getApplicationEventMulticaster拿到事件广播器,然后将事件广播出去。(这里后文也会详细交代)

三、如果有父容器,比如springMVC有父容器spring等。那么要再父容器内也将此事件进行广播。

事件广播

这里需要重点说一下广播事件这个部分

//大部分时间会走else分支(看下文解释)
if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else {
			getApplicationEventMulticaster().multicastEvent(applicationEvent,eventType);
	}
复制代码

earlyApplicationEvents

earlyApplicationEvents是AbstractApplicationContext中定义的一个字段,代码如下所示。

@Nullable
private Set<ApplicationEvent> earlyApplicationEvents;
复制代码

这里可以看出两点:

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

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

protected void prepareRefresh() {
		//省略无关代码
  	....
    //对earlyApplicationEvents进行初始化
		this.earlyApplicationEvents = new LinkedHashSet<>();
	}
复制代码

earlyApplicationEvents用来存放容器启动后需要发布的事件。它会在容器启动的prepareRefresh环节初始化为一个LinkedHashSet。(如果对spring的初始化流程不熟悉,请参考附录中的内容)

即放在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)

getApplicationEventMulticaster()又是何方神圣?

ApplicationEventMulticaster

getApplicationEventMulticaster实际上返回的是一个事件广播器。

ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
		if (this.applicationEventMulticaster == null) {
			throw new IllegalStateException("ApplicationEventMulticaster not initialized - "+"call 'refresh' before multicasting events via the context: "+this);
    }
		return this.applicationEventMulticaster;
	}
复制代码

同earlyApplicationEvents一样,applicationEventMulticaster也是抽象上下文(AbstractApplicationContext)中的一个属性。它会在spring容器启动的initApplicationEventMulticaster环节进行初始化(spring容器启动流程参见附录)

protected void initApplicationEventMulticaster() {
  //步骤A
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	if(beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) 			{
    this.applicationEventMulticaster =beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME,ApplicationEventMulticaster.class);
			//省略无关代码
			}
		}
  //步骤B
		else {
			this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
    beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME,this.applicationEventMulticaster);
				//省略无关代码
		}
	}
复制代码

对applicationEventMulticaster的初始化分为两种情况

一、当beanFactory中存在名为"ApplicationEventMulticaster"的bean时,直接取beanFactory中的bean对applicationEventMulticaster进行赋值。

二、如果beanFactory中没有对应的bean,则new SimpleApplicationEventMulticaster()赋值给applicationEventMulticaster,并将此类注册至beanFactory中。

总结一下,最终替你自定义类进行事件发布的类是:SimpleApplicationEventMulticaster。

SimpleApplicationEventMulticaster

SimpleApplicationEventMulticaster是替你进行事件发布的最终“幕后帮手”。其核心方法是:multicastEvent

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType)
  {
		ResolvableType type = (eventType != null ? eventType :resolveDefaultEventType(event));
		for (final ApplicationListener<?> listener :getApplicationListeners(event, type)) {
			Executor executor = getTaskExecutor();
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}
复制代码

其中的for循环就是将容器中所有的监听器全部拿出来,挨个执行各个监听器的onApplicationEvent方法(还记得文初提到的,重写onApplicationEvent方法吗?就是在这里被调用的),然后对应事件类型的监听器就会进行响应。需要注意的是,当容器中有线程池时,会通过线程池多线程调用onApplicationEvent,这样保证了效率,确保不会因为事件太多而发生阻塞现象。

那么你可能会有疑问,for循环遍历所有的监听器,它怎么知道所有的监听器分布在哪里呢?其中的关键在getApplicationListeners

getApplicationListeners方法不是SimpleApplicationEventMulticaster的方法,而是继承于其父类AbstractApplicationEventMulticaster的方法

AbstractApplicationEventMulticaster是一个管理监听器的“罐子”,它存储监听器,并且提供了管理监听器的各种方法,当然也包括getApplicationListeners

至此,我们就基本搞清楚了spring从发布事件到监听、处理事件的基本流程。最后做一下总结吧。

总结

  1. 在spring中,我们可以通过实现ApplicationContextAware接口,获取spring容器的ApplicationContext实例
  2. ApplicationContext接口中有publishEvent方法,可以发布事件。但是并非直接发布,而是将其委托给其父接口ApplicationEventPublisher的实现类AbstractApplicationContext。
  3. AbstractApplicationContext中对publishEvent方法进行了实现,发布事件的核心方法的原理是:获取spring容器中管理的监听器,然后for循环容器中的listener,对应事件的listener实现类的onApplication方法会被调用,实现对事件的响应。
  4. spring容器的监听器会在容器初始化时被注册进来,放在AbstractApplicationEventMulticaster。AbstractApplicationEventMulticaster提供了众多方法,实现了对listener的管理。

附录

给自己的公粽号打个广告,欢迎围观“趣论编程”。

下边是spring容器启动的核心步骤,只是简单罗列一下,以后再展开说,这里边流程还是挺多的。

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
      //前期准备工作
			prepareRefresh();

      //告诉子类去刷新内部的bean工厂
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
	
      //准备好bean工厂,下边要用
			prepareBeanFactory(beanFactory);

			try {
        //BeanFactoryPostProcessor是BeanFactory的后置处理器,针对BeanFactory实现各种功能扩展。
				postProcessBeanFactory(beanFactory);
				invokeBeanFactoryPostProcessors(beanFactory);

				// 注册bean processors 
				registerBeanPostProcessors(beanFactory);

        //用于国际化
				initMessageSource();

        //!!!初始化ApplicationEventMulticaster就是在这里!!!!
				initApplicationEventMulticaster();

				// 核心步骤
				onRefresh();

				// !!注册所有的监听器
				registerListeners();

				// 实例化所有未被实例化的饿汉式单例.
				finishBeanFactoryInitialization(beanFactory);

				// 完成容器初始化,发布相应的事件.
				finishRefresh();
			}

			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// 销毁实例
				destroyBeans();

				// 将 'active' 标志重置.
				cancelRefresh(ex);
				throw ex;
			}
			finally {
				resetCommonCaches();
			}
		}
	}
复制代码
文章分类
后端
文章标签