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


    近期疫情形势严峻,情形不容乐观,周末也不敢出去浪了,躲在家里“葛优躺”。闲来无事,又翻了遍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);
    }
    
    • 1
    • 2
    • 3

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

    监听消息

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

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

    this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
    
    • 1

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

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

    案例分析

    refresh()finishRefresh()方法中,

    publishEvent(new ContextRefreshedEvent(this));
    
    • 1

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

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

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    自定义广播
    @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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

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

    自定义监听
    @Component
    public class MyEventListener implements ApplicationListener<MyEvent> {
    
        @Override
        public void onApplicationEvent(MyEvent event) {
            System.out.println("我监听到你的消息了");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

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

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

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

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

  • 相关阅读:
    【Java】switch-case 和 if-else 的运行效率差异
    网上智慧教育云vr实验室管理系统促进教学公平和普及
    [附源码]java毕业设计海南生鲜冷链物流配送系统论文
    Java中的抽象类和接口
    uniapp - 微信小程序实现腾讯地图位置标点展示,将指定地点进行标记选点并以一个图片图标展示出来(详细示例源码,一键复制开箱即用)
    【Linux学习】高并发服务器框架 线程池介绍+线程池封装
    JavaWeb开发之——MySQL数据模型(04)
    点云目标检测——pointpillars环境配置与训练
    连续仨月霸占牛客榜首,京东T8呕心巨作:700页JVM虚拟机实战手册
    JavaSE、JavaEE与Spring的概念和异同点剖析以及规范13 个分析
  • 原文地址:https://blog.csdn.net/Qingai521/article/details/131548884