【Spring】从Spring源码入手分析广播与监听并完成项目实战

文章目录

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

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

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

源码导入教程也给你准备好了,不来试试吗?

版本号:spring-framework-5.0.x

源码解析

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

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

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

发送广播

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

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

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

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

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

java 复制代码
if (bean instanceof ApplicationEventPublisherAware) {
    ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}

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

监听消息

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

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

java 复制代码
this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);

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

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

案例分析

refresh()finishRefresh()方法中,

java 复制代码
publishEvent(new ContextRefreshedEvent(this));

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

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

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

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

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

消息与监听绑定

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

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

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

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

实战教学

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

自定义事件

java 复制代码
public class MyEvent extends ApplicationContextEvent {
    public MyEvent(ApplicationContext source) {
        super(source);
    }
}

自定义广播

java 复制代码
@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 方法。

自定义监听

java 复制代码
@Component
public class MyEventListener implements ApplicationListener<MyEvent> {

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

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

客户端

java 复制代码
@RestController
@RequestMapping("/demo")
public class DemoTest {

    @Autowired
    private MyPublisher myPublisher;

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

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

xml 复制代码
我要开始发送消息了。。。
我监听到你的消息了

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

看完文章,再看看我为了写这篇文章又日渐稀少的头发,我忍不住哭出声来。可能只有给我点赞,才能平复我的心情吧。

好看的皮囊千篇一律,有趣的灵魂万里挑一,让我们在冷漠的城市里相互温暖,我是阿Q,我们下期再见!

相关推荐
草莓base15 分钟前
【手写一个spring】spring源码的简单实现--bean对象的创建
java·spring·rpc
Karoku06636 分钟前
【企业级分布式系统】ELK优化
运维·服务器·数据库·elk·elasticsearch
乌啼霜满天24943 分钟前
Spring 与 Spring MVC 与 Spring Boot三者之间的区别与联系
java·spring boot·spring·mvc
Elaine2023911 小时前
零碎04 MybatisPlus自定义模版生成代码
java·spring·mybatis
小技与小术2 小时前
数据库表设计范式
数据库·mysql
安迁岚2 小时前
【SQL Server】华中农业大学空间数据库实验报告 实验三 数据操作
运维·服务器·数据库·sql·mysql
安迁岚2 小时前
【SQL Server】华中农业大学空间数据库实验报告 实验九 触发器
数据库·sql·mysql·oracle·实验报告
Loganer2 小时前
MongoDB分片集群搭建
数据库·mongodb
LKID体2 小时前
Python操作neo4j库py2neo使用之创建和查询(二)
数据库·python·neo4j
刘大浪2 小时前
后端数据增删改查基于Springboot+mybatis mysql 时间根据当时时间自动填充,数据库连接查询不一致,mysql数据库连接不好用
数据库·spring boot·mybatis