在 IoC 容器启动流程中有一个 finishRefresh 方法,具体实现如下:

 protected void finishRefresh() {clearResourceCaches();initLifecycleProcessor();getLifecycleProcessor().onRefresh();// 向所有监听 ContextRefreshedEvent 事件的监听者发布事件publishEvent(new ContextRefreshedEvent(this));LiveBeansView.registerApplicationContext(this);}

这里我们只关注 publishEvent 方法,这个方法用于发布 IoC 刷新完成事件,事件时如何发布的呢?下面我们一起来看一下。

一、原理分析

 @Overridepublic void publishEvent(ApplicationEvent event) {publishEvent(event, null);}protected void publishEvent(Object event, @Nullable ResolvableType eventType) {Assert.notNull(event, "Event must not be null");// Decorate event as an ApplicationEvent if necessaryApplicationEvent applicationEvent;// 根据事件类型进行包装if (event instanceof ApplicationEvent) {applicationEvent = (ApplicationEvent) event;} else {applicationEvent = new PayloadApplicationEvent<>(this, event);if (eventType == null) {eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();}}// Multicast right now if possible - or lazily once the multicaster is initializedif (this.earlyApplicationEvents != null) {this.earlyApplicationEvents.add(applicationEvent);} else {// 获取 ApplicationEventMulticaster,将事件广播出去getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);}// Publish event via parent context as well...// 判断是否存在父容器,如果存在则将事件也发布到父容器的监听者if (this.parent != null) {if (this.parent instanceof AbstractApplicationContext) {((AbstractApplicationContext) this.parent).publishEvent(event, eventType);} else {this.parent.publishEvent(event);}}}

通过上面方法可以看出 Spring 的事件是通过 ApplicationEventMulticaster 广播出去的,这个 ApplicationEventMulticaster 在 IoC 启动流程 initApplicationEventMulticaster 方法中初始化。如果该容器还存在父容器,那也会以同样的形式将事件发布给父容器的监听者。

 @Overridepublic 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) {// 如果执行器部位 null,则异步执行将事件发布给每一个监听者executor.execute(() -> invokeListener(listener, event));}else {// 同步发布事件invokeListener(listener, event);}}}

发布事件前会先获取所有已注册的监听器,而监听器早已在 IoC 启动流程的 registerListeners 方法中注册。获取到所有事件监听器之后,就可以进行事件发布了。发布的时候分为异步执行与顺序执行,默认情况下 executor 是没有初始化的,因此是顺序执行。

 protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {// 获取错误处理机制ErrorHandler errorHandler = getErrorHandler();if (errorHandler != null) {try {// 事件发布doInvokeListener(listener, event);}catch (Throwable err) {errorHandler.handleError(err);}}else {doInvokeListener(listener, event);}}private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {try {// 执行监听器的 onApplicationEvent 方法listener.onApplicationEvent(event);}catch (ClassCastException ex) {String msg = ex.getMessage();if (msg == null || matchesClassCastMessage(msg, event.getClass())) {// Possibly a lambda-defined listener which we could not resolve the generic event type for// -> let's suppress the exception and just log a debug message.Log logger = LogFactory.getLog(getClass());if (logger.isTraceEnabled()) {logger.trace("Non-matching event type for listener: " + listener, ex);}}else {throw ex;}}}

到这里 Spring 的事件通知机制流程就结束了,总的来说还是比较好理解的。

二、事件通知 demo

了解了事件通知机制的基本原理后,下面我们来写个 demo 体验一下监听器是如何使用的。参考自:Event事件通知机制

// 定义一个 Event
public class EventDemo extends ApplicationEvent {private static final long serialVersionUID = -8363050754445002832L;private String message;public EventDemo(Object source, String message) {super(source);this.message = message;}public String getMessage() {return message;}
}// 定义一个监听器1
public class EventDemo1Listener implements ApplicationListener<EventDemo> {public void onApplicationEvent(EventDemo event) {System.out.println(this + " receiver " + event.getMessage());}
}// 定义一个监听器2
public class EventDemo2Listener implements ApplicationListener<EventDemo> {public void onApplicationEvent(EventDemo event) {System.out.println(this + " receiver " + event.getMessage());}
}// 定义一个事件发布者
public class EventDemoPublish {public void publish(ApplicationEventPublisher applicationEventPublisher, String message) {EventDemo eventDemo = new EventDemo(this, message);applicationEventPublisher.publishEvent(eventDemo);}
}

在 XML 中配置 bean:

    <bean id="eventDemoPublish" class="com.jas.mess.event.EventDemoPublish"/><bean id="eventDemo1Listener" class="com.jas.mess.event.EventDemo1Listener"/><bean id="eventDemo2Listener" class="com.jas.mess.event.EventDemo2Listener"/>

编写测试类:

    @Testpublic void eventTest() {ApplicationContext applicationContext = new ClassPathXmlApplicationContext(configLocation);applicationContext.getBean("eventDemoPublish", EventDemoPublish.class).publish(applicationContext, "hello world");}

控制台输出:

不知道你有没有注意到,在发布事件的时候我们传的发布者是 applicationContextapplicationContext 本身继承自 ApplicationEventPublisher 接口,因此它本身也是一个事件发布者。

参考阅读

Event事件通知机制 by wangqi

Spring IoC 源码系列(三)Spring 事件发布机制原理分析相关推荐

  1. Spring IoC 源码系列(五)getBean 流程分析

    一.FactoryBean 用法讲解 在分析源码流程之前,我们先来看一下 FactoryBean,乍一看这家伙和 BeanFactory 很像,它们都可以用来获取 bean 对象,简单来说 Facto ...

  2. Spring IoC 源码系列(一)BeanDefinition 初始化与注册

    一.BeanDefinition 1.1 什么是 BeanDefinition 在一般的 Spring 项目中,主要通过 XML 的方式配置 bean,而 BeanDefinition 就是 XML ...

  3. Spring IoC 源码系列(四)bean创建流程与循环依赖问题分析

    创建单例 bean 的代码细节在 org.springframework.beans.factory.support.AbstractBeanFactory#getBean 中,getBean 顾名思 ...

  4. JVM源码系列:ThreadMXBean 打出堆栈信息原理分析

    我们通常会使用工具jstack 去跟踪线程信息,其如何实现使用attach 的方式还是ptrace 的方式,这些可以去参考本人的博客的其他文章. 但这些方式都是外部使用的方式,如何直接使用java代码 ...

  5. Spring IoC 源码导读

    源码记录:spring-framework-5.1.7-source-code-read 文章导读 Spring IoC 源码系列(一)BeanDefinition 初始化与注册 Spring IoC ...

  6. Spring IoC源码:getBean 详解

    文章目录 Spring源码系列: 前言 正文 方法1:getObjectForBeanInstance 方法2:getObjectFromFactoryBean 方法3:doGetObjectFrom ...

  7. Spring读源码系列之AOP--03---aop底层基础类学习

    Spring读源码系列之AOP--03---aop底层基础类学习 引子 Spring AOP常用类解释 AopInfrastructureBean---免被AOP代理的标记接口 ProxyConfig ...

  8. Spring AOP 源码系列(一)解析 AOP 配置信息

    在进行源码阅读之前建议先看一下这篇文章:Spring AOP 源码分析系列文章导读 by 田小波,写的非常好,推荐阅读. 关于 AOP 中常用的一些术语这里就不解释了,如果不清楚的建议先看一遍上面推荐 ...

  9. 【Spring源码系列】Spring注解扫描-@ComponentScan底层原理解读

    这里写目录标题 前言 一.Spring扫描-@ComponentScan注解介绍 @ComponentScan作用 @ComponentScan重要参数 二.Spring扫描-源码分析 声明关键点 源 ...

最新文章

  1. Blender创建三维教室场景学习教程 3D Classroom Environment Creation in Blender
  2. iphone通讯录批量删除_iPhone通讯录删除了如何恢复?用对方法快速找回,亲测有效!_...
  3. android 删除模拟器,android – 如何从avd设备中删除脱机模拟器?
  4. PHP实现一个轻量级容器
  5. 你不知道的Event Loop
  6. I2C通信读写数据过程
  7. 医疗大数据分析需考虑哪些因素
  8. typescript step by step interface class
  9. CMM3下的应用及改进
  10. 【Python】爬取xici和快代理的免费代理ip
  11. 扫雷游戏计算机版,扫雷经典版电脑版
  12. eeupdate使用说明_UNRAID中文插件分享以及部分问题解决方案
  13. 计算机网络按覆盖地域分为,计算机网络按其所覆盖的地域范围一般可分为________ 。...
  14. wamp3.1.4下载及WampServer下增加多版本PHP
  15. 三小时学会Kubernetes:容器编排详细指南
  16. 为什么我们的数据还不够开放?
  17. 支付宝H5,微信H5,微信公众号支付回调
  18. mac设置文件权限_如何在Mac上设置文件权限
  19. FreeType字体引擎介绍
  20. 项目经理之新任项目经理的五项修炼

热门文章

  1. mysql 一次性导入数据库_Mysql 一次性备份导出/导入恢复所有数据库
  2. 第三方分享接口api
  3. nacos作注册中心+feign接口调用进行服务提供和服务消费代码示例
  4. 操作系统 课堂练习题01【15道 经典题目】
  5. Android 性能优化——绘制优化
  6. JAVA多线程中wait()方法的详细分析
  7. HDU 4323 bk树 编辑距离
  8. 解决mysql操作1045错误,1153错误和1130错误
  9. 计算机科学与技术专业导向ppt,计算机科学与技术专业导向讲座 第讲.ppt
  10. html长图滚动,Axure教程:长页或长图滚动效果