写点什么

深入了解 Spring 之事件机制

用户头像
邱学喆
关注
发布于: 1 小时前
深入了解Spring之事件机制

一. 概述

深入了解 Spring 之上下文大体介绍了 ApplicationContext 启动过程中关键的几个步骤;在 AbstractApplicationContext 中,有三个关键属性成员,如下:

  • applicationEventMulticaster 事件的传播者

  • applicationListeners 监听器集合

  • earlyApplicationEvents 早期上下文启动过程中触发的事件;

这三个属性成员就是这篇所要介绍的内容,事件机制

这里,我们带着问题去了解:

  • 工作机制是什么样的?

  • 事件是如何触发的?

  • 事件是如何传播的?

  • 监听器是如何添加的?

二. 概念及原理

事件机制,涉及到几大概念,事件、发布者、观察者、监听者,,其采用的模式是观察者模式;这个时候,值得推荐的刘伟大佬的文章,非常值得一读,通俗介绍了观察者模式,以及大体的原理,这里就不在班门弄斧;

整个设计模式完整版,史上最全设计模式导学目录(完整版)

三. spring 实现

1. 事件

在 spring 中,抽象类为 ApplicationEvent,该抽象类有两个成员属性

  • timestamp 记录事件的生产时间戳

  • source 事件所携带的对象,该对象是 Object,意味我们我们可以传递任意的类型;

在 spring 中,有几大关键的子类,涉及到从容器启动到初始化完成的事件对象;

  • ContextClosedEvent spring 容器关闭事件

//AbstractApplicationContext类protected void doClose() {		if (this.active.get() && this.closed.compareAndSet(false, true)) {			//.....			// Publish shutdown event.			publishEvent(new ContextClosedEvent(this));			//.......		}	}
复制代码
  • ContextRefreshedEvent spring 容器的初始化后或者刷新完成事件;具体代码如下

//AbstractApplicationContext类protected void finishRefresh() {		//......		// Publish the final event.		publishEvent(new ContextRefreshedEvent(this))    //  	}
复制代码
  • ContextStoppedEvent spring 容器停止事件

//AbstractApplicationContext类public void stop() {	getLifecycleProcessor().stop();	publishEvent(new ContextStoppedEvent(this));}
复制代码
  • ContextStartedEvent spring 容器初始化开始事件

//AbstractApplicationContext类@Overridepublic void start() {  getLifecycleProcessor().start();  publishEvent(new ContextStartedEvent(this));}
复制代码

ContextStartedEvent 于 ContextStoppedEvent 是有关 Lifecycle 的事件;而 ContextClosedEvent 是有关整个 spring 容器销毁;

下面是有关 spring boot 所拓展的事件

  • ApplicationEnvironmentPreparedEvent 容器环境对象初始化后的事件

  • ApplicationPreparedEvent 容器初始化前的事件,主要是在做 refresh 动作之前做触发的事件

  • ApplicationStartedEvent 容器已经完成 refresh 动作后所触发的事件

  • ApplicationReadyEvent 容器已经运行中的事件

  • ApplicationFailedEvent 容器初始化失败所触发的事件

  • ApplicationStartingEvent 容器开始时所触发的事件

它们之间的触发顺序如下:

ApplicationStartingEvent ->ApplicationEnvironmentPreparedEvent -> ApplicationPreparedEvent ->ContextStartedEvent -> ContextRefreshedEvent ->ApplicationStartedEvent->ApplicationReadyEvent

2. 传播者

这里介绍关键的接口 ApplicationEventMulticaster,如类图关系如下:

ApplicationEventMulticaster 暴露了对监听器的添加删除,以及传播事件接口;为了更快获取对应的事件的监听器,添加一个缓存;为了避免并发问题,添加锁机制;有关监听器的添加删除,都是对 defaultRetriever 进行操作;

在 SimpleApplicationEventMulticaster 类中有两个属性,taskExecutor 是线程池对象,意味着监听器可以异步去处理事件;ErrorHandler 监听器处理事件异常时会有该接口对应的实现类进行处理。

然而这个 ApplicationEventMulticaster 是 spring 的内部实现逻辑。对外的提供的接口为 ApplicationEventPublisher,其暴露的接口如下:

从上面的类图来看,我们可以调用 ApplicationContext 的实现类对象,就可以发布事件;

那么,我们是如何拿到这个 ApplicationContext 对象的呢?

我们向 BeanFactory 注入 Bean,该 Bean 实现 ApplicationEventPublisherAware 该接口,BeanFactory 就会自动注入 ApplicationContext 对象到 Bean 对象中去;

3. 监听器

applicationListener 接口的类图如下:

我们只要找对应的对应的实现类,查阅里面的逻辑即可;这里不在对其的实现类进行介绍;

我们更加的关心的监听器是如何添加到上下文中去的;

方式一: 可以通过 ApplicationContext 上下文进行添加

方式二: 从 BeanFactory 容器中找出 ApplicationListener 类型的对象;

值得说的是:我们可以通过 @EventListener 注解,可以注入;其原理是由 EventListenerMethodProcessor 去检测 BeanFactory 容器中标有该注解的方法,将其方法通过 EventListenerFactory 接口的默认实现类 DefaultEventListenerFactory 来封装为 ApplicationListenerMethodAdapter 对象;另外也有事务的监听器 TransactionalEventListener,具体可以查阅深入理解 spring 框架之事务管理

四. spring Boot 事件机制

为什么单独抽出来呢,因为其是不归 spring 的上下文进行管控的,而是有 SpringApplication 进行管控的;

添加监听器是通过 spring.factories 配置文件进行添加默认的监听器

代码如下:

//SpringApplicationpublic SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {		//....		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));		this.mainApplicationClass = deduceMainApplicationClass();	}private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {		return getSpringFactoriesInstances(type, new Class<?>[] {});	}private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,			Class<?>[] parameterTypes, Object... args) {		ClassLoader classLoader = Thread.currentThread().getContextClassLoader();		// Use names and ensure unique to protect against duplicates		Set<String> names = new LinkedHashSet<>(				SpringFactoriesLoader.loadFactoryNames(type, classLoader));		List<T> instances = createSpringFactoriesInstances(type, parameterTypes,				classLoader, args, names);		AnnotationAwareOrderComparator.sort(instances);		return instances;	}
复制代码

事件发布者 SpringApplicationRunListener 接口默认的实现类 EventPublishingRunListener,也是通过 spring.factories 配置文件进行配置,类图如下;

代码如下:

//SpringApplicationpublic ConfigurableApplicationContext run(String... args) {  	//....  //初始化发布者以及添加对应的监听器		SpringApplicationRunListeners listeners = getRunListeners(args);  //触发开始动作-ApplicationStartingEvent事件		listeners.starting();		try {			ApplicationArguments applicationArguments = new DefaultApplicationArguments(					args);      //内部触发environmentPrepared动作-ApplicationEnvironmentPreparedEvent事件			ConfigurableEnvironment environment = prepareEnvironment(listeners,					applicationArguments);			configureIgnoreBeanInfo(environment);			Banner printedBanner = printBanner(environment);			context = createApplicationContext();			exceptionReporters = getSpringFactoriesInstances(					SpringBootExceptionReporter.class,					new Class[] { ConfigurableApplicationContext.class }, context);      //内部触发contextPrepared动作(不做任何处理)以及contextLoaded-ApplicationPreparedEvent事件			prepareContext(context, environment, listeners, applicationArguments,					printedBanner);			refreshContext(context);			afterRefresh(context, applicationArguments);			stopWatch.stop();			if (this.logStartupInfo) {				new StartupInfoLogger(this.mainApplicationClass)						.logStarted(getApplicationLog(), stopWatch);			}      //触发started动作-ApplicationStartedEvent事件			listeners.started(context);			callRunners(context, applicationArguments);		}		catch (Throwable ex) {      //内部触发failed动作-ApplicationFailedEvent事件			handleRunFailure(context, ex, exceptionReporters, listeners);			throw new IllegalStateException(ex);		}
try { //触发running动作-ApplicationReadyEvent事件 listeners.running(context); } catch (Throwable ex) { //内部触发failed动作-ApplicationFailedEvent事件 handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
复制代码

五. 总结

经过上述的初略的介绍,对 spring 的事件机制原理以及如何使用有了大体了介绍,以及 spring 初始化过程中所触发的事件都有哪些有了大体的介绍;所以,在业务代码方面,可以有效的运用该机制,进行代码的解耦,而不用创建轮子实现一套事件机制。


发布于: 1 小时前阅读数: 5
用户头像

邱学喆

关注

计算机原理的深度解读,源码分析。 2018.08.26 加入

在IT领域keep Learning。要知其然,也要知其所以然。原理的爱好,源码的阅读。输出我对原理以及源码解读的理解。个人的仓库:https://gitee.com/Michael_Chan

评论

发布
暂无评论
深入了解Spring之事件机制