源码级别的广播与监听实现

原创:微信公众号 【阿Q说代码】,欢迎分享,转载请保留出处。

近期疫情形势严峻,情形不容乐观,周末也不敢出去浪了,躲在家里”葛优躺”。闲来无事,又翻了遍 Spring的源码。不翻不知道,一翻吓一跳,之前翻过的源码已经吃进了肚子里,再见亦是陌生人。

个人建议:为了以后能快速的捡起某个知识点,最好的方法还是形成文档,下次有遗漏的时候,直接读文档,按之前的思路捋一遍,”干净又卫生”。

之前的文章中我们已经介绍过如何在项目中快速上手”事件通知机制”,相信大家已经掌握了。但是我们作为高级 javaer,要知其然,更要知其所以然。今天就带大家从源码的角度来分析一下 广播与监听的底层实现原理。

源码导入教程也给你准备好了,不来试试吗?
版本号:spring-framework-5.0.x

源码解析

为了实现广播与监听的功能, Spring为我们提供了两个重要的函数式接口: ApplicationEventPublisherApplicationListener。前者的 publishEvent()方法为我们提供了发送广播的能力;后者的 onApplicationEvent()方法为我们提供了监听并处理事件的能力。

接下来我们就来分析一下 spring是如何运用这两种能力的。

不知道大家对单例对象的初始化调用过程是否熟悉?主要调用方法流程如下:

源码级别的广播与监听实现

发送广播

applyBeanPostProcessorsBeforeInitialization方法会去遍历该工厂创建的所有的 Bean后置处理器,然后去依次执行后置处理器对应的 postProcessBeforeInitialization方法。

在该方法的实现类中我们看到了两个熟悉的类名

源码级别的广播与监听实现

不知道大家还记得不,这俩类是在 beanFactory的准备工作过程中添加的两个 bean的后置处理器,所以这个地方会依次去执行这两个类中的实现方法。

源码级别的广播与监听实现

由于 蓝框中类的实现方法是默认实现按照原样返回的给定的 bean,所以此处不用过多分析,我们重点来看下 红框中类的方法实现。

该方法中最重要的是 invokeAwareInterfaces方法,它的作用是检测对应的 bean是否实现了某个 Aware接口,如果实现了的话就去进行相关的调用。

if (bean instanceof ApplicationEventPublisherAware) {
    ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}

我们发现在 invokeAwareInterfaces方法中出现了如上代码,这不就是和广播发送相关的吗?所以只要我们写一个类来实现 ApplicationEventPublisherAware接口,就可以在该 bean中注入一个 ApplicationEventPublisher对象,也就获得了发送广播的能力。

监听消息

applyBeanPostProcessorsAfterInitialization方法 会去遍历该工厂创建的所有的 Bean后置处理器,然后去依次执行后置处理器对应的 postProcessAfterInitialization方法。

同样的,该方法的实现类中也有 ApplicationContextAwareProcessorApplicationListenerDetector两个类,但是不同的是,前者的类的实现方法是默认实现按照原样返回的给定 bean,而后者做了相关的处理。

this.applicationContext.addApplicationListener((ApplicationListener) bean);

上述代码是将实现了 ApplicationListener接口的 bean添加到监听器列表中,最终是保存在 AbstractApplicationEventMulticaster的成员变量 defaultRetriever的集合 applicationListeners中。

猜想:当发送广播消息时,就直接找到集合中的这些监听器,然后调用每个监听器的 onApplicationEvent方法完成事件的处理。

案例分析

refresh()finishRefresh()方法中,

publishEvent(new ContextRefreshedEvent(this));

发送一条事件类型为 ContextRefreshedEvent的广播消息,用来代表 Spring容器初始化结束。通过分析发现,该方法中最主要的就是如下代码:

//真正的广播交给 applicationEventMulticaster 来完成
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

refresh()initApplicationEventMulticaster()applicationEventMulticaster初始化为 SimpleApplicationEventMulticaster

在实现类 SimpleApplicationEventMulticaster的方法中,会找到已注册的 ApplicationListener列表,然后分别调用 invokeListener方法(将监听和事件作为参数传到方法并执行的过程就是发送广播的过程)。

底层调用的是 listener.onApplicationEvent(event);方法,也就是各个监听实现类单独处理广播消息的逻辑。

消息与监听绑定

看到这儿,你是不是已经发现了:消息类型和监听器的绑定发生在广播过程中。接下来就让我们去一探究竟

我们看一下 multicastEvent()方法中的 getApplicationListeners(event, type)方法。

在该方法中,用到了 ConcurrentHashMap类型的缓存 retrieverCache,所以每种类型的事件在广播的时候会触发 一次绑定操作。它的 key由事件的来源和类型确定,它的 value中就包含了由事件来源和类型所确定的所有监听列表。

其中绑定的逻辑就出现在 retrieveApplicationListeners方法中,大家可以去源码中查看。

实战教学

纸上得来终觉浅,绝知此事要躬行。为了更好地理解广播与监听的流程,我们当然得用实战来加以辅佐!

自定义事件

public class MyEvent extends ApplicationContextEvent {
    public MyEvent(ApplicationContext source) {
        super(source);
    }
}

自定义广播

@Component
public class MyPublisher implements ApplicationEventPublisherAware, ApplicationContextAware {

    private ApplicationEventPublisher applicationEventPublisher;

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    //发送广播消息
    public void publishEvent(){
        System.out.println("我要开始发送消息了。。。");
        MyEvent myEvent = new MyEvent(applicationContext);
        applicationEventPublisher.publishEvent(myEvent);
    }
}

MyPublisher实现了 ApplicationEventPublisherAware接口 ,在 spring初始化(见上文中的 invokeAwareInterfaces)的时候会回调 setApplicationEventPublisher方法,获取到初始化(添加 bean后置处理器 ApplicationContextAwareProcessor)时的 AbstractApplicationContext,而 AbstractApplicationContext又间接实现了 ApplicationEventPublisher而获得发送能力。真正执行的是 AbstractApplicationContext 类中的 publishEvent 方法。

自定义监听

@Component
public class MyEventListener implements ApplicationListener {

    @Override
    public void onApplicationEvent(MyEvent event) {
        System.out.println("我监听到你的消息了");
    }
}

MyEventListener实现了 ApplicationListener接口,在 spring初始化(见上文中的 addApplicationListener)的时候会添加到 applicationListeners中,在执行 publishEvent 方法时就会走 MyEventListener中的 onApplicationEvent方法。

客户端

@RestController
@RequestMapping("/demo")
public class DemoTest {

    @Autowired
    private MyPublisher myPublisher;

    @RequestMapping("/test")
    public void test() {
        myPublisher.publishEvent();
    }
}

访问 127.0.0.1:8008/demo/test就可以发送广播了,发送与监听内容如下:

我要开始发送消息了。。。
我监听到你的消息了

看到这儿,相信你己经完全掌握了广播与监听的精髓了,赶快实践起来吧。阿Q将持续更新 java实战方面的文章,感兴趣的可以关注下,也可以来 技术群讨论问题呦!

Original: https://www.cnblogs.com/aqsaycode/p/16112678.html
Author: 阿Q说代码
Title: 源码级别的广播与监听实现

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/569988/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 全链路spring cloud sleuth+zipkin

    一、About ZipKin please google 二、 Demo Scene 三、 Result Display 四、Prepare 1、soft version kafk…

    Java 2023年5月30日
    0106
  • 后端开发学习记录(二)——MySQL的学习

    MySQL 一、什么是数据库 数据库:(DB,DataBase) 概念:数据仓库,安装在操作系统之上(windows,linux,mac…)之上 作用:储存数据,管理数…

    Java 2023年6月13日
    085
  • 我又不是你的谁–java instanceof操作符用法揭秘

    背景故事 《曾经最美》是朱铭捷演唱的一首歌曲,由陈佳明填词,叶良俊谱曲,是电视剧《水晶之恋》的主题曲。歌曲时长4分28秒。 歌曲歌词: 看不穿你的眼睛 藏有多少悲和喜 像冰雪细腻又…

    Java 2023年5月29日
    0108
  • 服务调用过程

    前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 源码分析均基于官方Demo,路径:dubbo/dubbo-demo 如果没有…

    Java 2023年6月16日
    073
  • MySQL查询为什么没走索引?这篇文章带你全面解析

    工作中,经常遇到这样的问题,我明明在MySQL表上面加了索引,为什么执行SQL查询的时候却没有用到索引? 同一条SQL有时候查询用到了索引,有时候却没用到索引,这是咋回事? 原因可…

    Java 2023年6月8日
    0144
  • 访问修饰符你用对了吗

    不知道大家在平时的开发过程中有没有注意到访问修饰符,哈哈哈,有没有懵,在java中有哪些访问修饰符,还记得清吗?今天想分享下访问修饰符的哪些小事。 一、访问修饰符有哪些 在java…

    Java 2023年6月9日
    063
  • Java 反射机制

    基本概念 在Java 运行时环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法? 答案是 肯定的。 这种动态获取类的信息以及动态调用…

    Java 2023年5月29日
    087
  • 程序员要知道的22个学习网站

    点击标题即可直达链接网址 GitHub是一个面向开源及私有软件项目的托管以及在线软件开发平台,用于存储、跟踪和协作软件项目,开发者能够上传自己的代码文件,并与其他开发者在开源项目上…

    Java 2023年6月15日
    070
  • 一文搞懂Kafka的基本原理及使用

    Kafka的基本原理及使用 一、基本概念及原理 1、Kafka特点 Kafka 是一个分布式的流式平台,流式平台包括以下三个特点: 发布和订阅消息(流),类似于一个消息队列或企业消…

    Java 2023年6月8日
    098
  • spring上传文件

    本文将说明spring上传文件如何配置,以及从request请求中解析到文件流的原理 #添加依赖 主要用来解析request请求流,获取文件字段名、上传文件名、content-ty…

    Java 2023年6月9日
    089
  • C语言实现一个走迷宫小游戏(深度优先算法)

    补充一下,先前文章末尾给出的下载链接的完整代码含有部分C++的语法(使用Dev-C++并且文件扩展名为.cpp的没有影响),如果有的朋友使用的语言标准是VC6的话可能不支持,所以在…

    Java 2023年6月8日
    0106
  • Mybatis-Plus

    国产的开源框架,基于 MyBatis 核心功能就是简化 MyBatis 的开发,提高效率。 Spring Boot(2.3.0) + MyBatis Plus(国产的开源框架,并没…

    Java 2023年6月15日
    099
  • Mybatis SqlNode源码解析

    1.ForEachSqlNode mybatis的foreach标签可以将列表、数组中的元素拼接起来,中间可以指定分隔符separator <select id="…

    Java 2023年6月9日
    089
  • 【年度钻石】Linux云计算+运维(1)《博学谷》黑马

    Java互联网企业架构技术VIP课程【腾讯课堂每特】 Java互联网企业架构技术VIP课程【腾讯课堂每特】 课程 内容 站在架构角度,基于装饰模式纯手写设计多级缓存框架 本节课需要…

    Java 2023年6月7日
    073
  • 利用docker部署elk交换机日志分析

    今天我们来聊一下利用docker部署elk日志分析系统,这里解析一下elk是啥东西。elk分别是Elasticsearch,Logstash和Kibana的首字母缩写。 Elast…

    Java 2023年6月8日
    068
  • 实力总结四类Bean注入Spring的方式

    一提到 Spring,大家最先想到的是啥?是 AOP和 IOC的两大特性?是 Spring中 Bean的初始化流程?还是基于 Spring的 Spring Cloud全家桶呢? 今…

    Java 2023年6月5日
    0116
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球