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

4254e157ed2aa62239c3696484c016e6.gif

作者 | 阿Q

来源 | 阿Q说代码

闲来无事,又翻了遍Spring的源码。不翻不知道,一翻吓一跳,之前翻过的源码已经吃进了肚子里,再见亦是陌生人。

今天就带大家从源码的角度来分析一下广播与监听的底层实现原理。

源码解析

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

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

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

3b5656cfcd193d47c7f0acb5e0385833.png

发送广播

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

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

07c8c899b50c2f5c99b58742d6d8f93e.png

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

c17df2bfa920ee818de130a848ddfb9f.png

由于蓝框中类的实现方法是默认实现按照原样返回的给定的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;@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {this.applicationEventPublisher = applicationEventPublisher;}@Overridepublic 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<MyEvent> {@Overridepublic void onApplicationEvent(MyEvent event) {System.out.println("我监听到你的消息了");}
}

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

客户端

@RestController
@RequestMapping("/demo")
public class DemoTest {@Autowiredprivate MyPublisher myPublisher;@RequestMapping("/test")public void test() {myPublisher.publishEvent();}
}

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

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

看到这儿,相信你己经完全掌握了广播与监听的精髓了,赶快实践起来吧。

dd144c3f5bfb869eda57a8120ba8b316.gif

往期推荐

如果让你来设计网络

用过留痕,谁动了我的档案?

一把王者的时间,我就学会了Nginx

如何在 Kubernetes Pod 内进行网络抓包

9ee2f75948e14b011e89c3cc29b9dff3.gif

点分享

1e6b3cd71edc61e644c09f5a1a081af9.gif

点收藏

1d87ad1775f2f8093225064135f4c349.gif

点点赞

a7d76c94c6586ffaa0c1c25b70d73f8f.gif

点在看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/512002.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用代码玩剧本杀?第3届83行代码大赛剧情官方解析

简介&#xff1a; 由阿里云云效主办的2021年第3届83行代码挑战赛已经收官。超2万人围观&#xff0c;近4000人参赛&#xff0c;85个团队组团来战。大赛采用游戏闯关玩儿法&#xff0c;融合元宇宙科幻和剧本杀元素&#xff0c;让一众开发者玩得不亦乐乎。 说到剧本杀&#xff0c…

阿里大规模业务混部下的全链路资源隔离技术演进

简介&#xff1a; 本文作为混部实践系列开篇&#xff0c;本篇文章将介绍资源隔离技术在混部中的重要性、其落地挑战及我们的应对思路。 作者&#xff1a;钱君、南异 混部顾名思义&#xff0c;就是将不同类型的业务在同一台机器上混合部署起来&#xff0c;让它们共享机器上的 …

探索PCIe 3.0峰值性能,长江存储推新消费级固态硬盘致态TiPlus5000

2022年4月8日&#xff0c;长江存储推出新款消费级固态硬盘产品致态TiPlus5000。该产品采用基于晶栈 2.0&#xff08;Xtacking 2.0&#xff09;架构的长江存储第三代三维闪存芯片&#xff0c;支持PCIe Gen3x4接口、NVMe 1.3协议&#xff0c;顺序读取速度高达3500 MB/s&#xff0…

“敏捷版”全链路压测

简介&#xff1a; PTS 结合 10 多年来阿里的全链路压测的经验&#xff0c;让阿里云的用户可以如同享用满汉全席般的享用全套标准的全链路压测&#xff0c;也可以根据自己的需求&#xff0c;选择最适合自己的方式。 作者&#xff1a;子矜 客户的故事 全链路压测被誉为大促备战…

linux传输tcp命令,Linux tcpdump命令帮助和示例

Tcpdump是用于网络数据包分析的基本命令行实用工具。它显示网络上的TCP/IP和其他传输的网络数据包&#xff0c;tcpdump 适用于大多数的类Unix系统操作系统(如Linux,BSD等)。类Unix系统的 tcpdump 需要使用libpcap这个捕捉数据的库就像 Windows下的WinPcap。Tcpdump使用libpcap库…

离线实时一体化数仓与湖仓一体—云原生大数据平台的持续演进

简介&#xff1a; 阿里云智能研究员 林伟 &#xff1a;阿里巴巴从湖到仓的演进给我们带来了湖仓一体的思考&#xff0c;使得湖的灵活性、数据种类丰富与仓的可成长性和企业级管理得到有机融合&#xff0c;这是阿里巴巴最佳实践的宝贵资产&#xff0c;是大数据的新一代架构。 林…

Kubernetes 入门教程

简介&#xff1a;本文是一篇 kubernetes&#xff08;下文用 k8s 代替&#xff09;的入门文章&#xff0c;将会涉及 k8s 的架构、集群搭建、一个 Redis 的例子&#xff0c;以及如何使用 operator-sdk 开发 operator 的教程。在文章过程中&#xff0c;会穿插引出 Pod、Deployment…

linux格式化usb设备,如何在 usb linux下格式化磁盘

慕田峪7331174以格式化 /dev/sda1 分区为例&#xff1a;$ sudo umount /dev/sda1# 必须先卸载该分区# 格式化为 FAT 分区$ sudo mkfs.vfat -F 32 /dev/sda1# -F 参数必须大写&#xff0c;参数有 12&#xff0c;16 和 32&#xff0c;分别对应 FAT12&#xff0c;FAT16&#xff0c…

通过浪潮AIStation实现细粒度高性能的GPU资源共享

作者 | 张荣国 供稿 | 浪潮 GPU&#xff08;Graphics Processing Unit&#xff09;&#xff0c;即图形处理器&#xff0c;是一种由大量核心组成的大规模并行计算架构&#xff0c;专为同时处理多重任务而设计。GPU在大规模并行运算上具有巨大优势&#xff0c;为大数据、人工智能…

阿里云发布云原生加速器,携手生态企业拥抱数字时代

简介&#xff1a; 继去年推出云原生合作伙伴计划之后&#xff0c;阿里云正式发布云原生加速器&#xff0c;携手生态企业拥抱数字时代。 今天&#xff0c;千行百业都在拥抱云计算、拥抱云原生&#xff0c;进行数字化创新升级。作为国内最早实践云原生的企业&#xff0c;阿里巴巴…

网不好怎么办?TLS握手带宽直降80%,BabaSSL是怎么做到的?| 龙蜥技术

简介&#xff1a; 为了保障数据的安全性&#xff0c;客户端会先和服务器进行 TLS 握手&#xff0c;有什么办法可以减少 TLS 握手的带宽消耗呢&#xff1f; 编者按&#xff1a;BabaSSL 是一款开源的密码库产品&#xff0c;在 GitHub 和龙蜥社区开源&#xff0c;并加入到龙蜥社区…

企业级数据湖实践

简介&#xff1a;2021云栖大会云原生企业级数据湖专场&#xff0c;阿里云智能高级解决方案架构师周皓为我们带来《企业级数据湖最佳实践》的分享。 本文主要分享了数据湖的核心能力及几个最佳实践案例。 以下是精彩视频内容整理 一、统一数据存储&#xff0c;多引擎对接&…

浅述 Docker 的容器编排

作者 | 天元浪子来源 | CSDN博客概述作为容器引擎&#xff0c;Docker为容器化的应用程序提供了开放标准&#xff0c;使得开发者可以用管理应用程序的方式来管理基础架构&#xff0c;实现快速交付、测试和部署代码。随着容器的大量使用&#xff0c;又产生了如何协调、调度和管理…

Cloudera CDP 企业数据云测试开通指导

简介&#xff1a; 基于阿里云部署的 Cloudera CDP 企业数据云平台已经进入公测阶段&#xff0c;本文详细介绍了相关试用/试用流程。 基于阿里云部署的 Cloudera CDP 企业数据云平台已经进入公测阶段&#xff0c;如对该平台感兴趣&#xff0c;可以使用下面的流程进行试用。 如需…

重装linux之后gcc等下载不了,Redhat linux下安装gcc

一、安装步骤1、使用whichgcc命令发现gcc没有安装2、拷贝gcc-3.2.2-5.i386.rpm 尝试安装说明在这之前还需要先装binutils、cpp、glibc-devel这三个包3、拷贝binutils-2.13.90.0.18-9.i386.rpmcpp-3.2.2-5.i386.rpmglibc-devel-2.3.2-11.9.i386.rpm 到install目录分别安装rpm -i…

解决 Serverless 落地困难的关键,是给开发者足够的“安全感”

简介&#xff1a;越来越多的云产品都会向全托管、Serverless 形态演进。当云的产品体系 Serverless 化达到一个临界值&#xff0c;通过函数计算这样的 Serverless 计算服务结合其他 Serverless 形态的云服务&#xff0c;能够完整的实现整个应用时&#xff0c;Serverless 就会变…

如何通过任务调度实现百万规则报警

简介&#xff1a;报警是一个公司的日常需求&#xff0c;常见的形态除了满足运维过程中的基础设施监控报警&#xff08;CPU/内存/磁盘等&#xff09;之外&#xff0c;部分公司也会在应用指标&#xff08;如 QPS、RT 等&#xff09;及业务指标&#xff08;如 GMV/日活 等&#xf…

linux无法关机 grub2,Ubuntu关机卡住无法关机如何解决?

电脑无法关机是最让人头疼的事&#xff0c;在Ubuntu系统中&#xff0c;有时会遇到关机卡住的情况&#xff0c;导致关不了机&#xff0c;遇到这种情况千万不要强制关机&#xff0c;下面小编就教你如何解决这个问题。解决方案&#xff1a;在终端用sudo vi打开/boot/grub/grub.cfg…

不用跑项目,组件效果所见即所得,绝了!

作者 | 零一来源 | 前端印象大家好&#xff0c;看到一个好东西&#xff0c;忍不住来分享一下。我们在写需求时都会封装一些组件&#xff0c;然后会为该组件定义一些 props &#xff0c;使其跟业务分离&#xff0c;变得更通用。写完组件后需要验证一下组件的效果&#xff0c;也就…

Kubernetes 已经成为云原生时代的安卓,这就够了吗?

简介&#xff1a;本文将介绍如何在 Kubernetes 上构建新的应用管理平台&#xff0c;提供一层抽象以封装底层逻辑&#xff0c;只呈现用户关心的接口&#xff0c;使用户可以只关注自己的业务逻辑&#xff0c;管理应用更快更安全。 作者&#xff1a;司徒放 导语&#xff1a;云原生…