Spring源码之七registerListeners()及发布订阅模式

大家好,我是程序员田同学。

今天带大家解读refresh()方法中的registerListeners()方法,也就是我们经常说的Spring的发布-订阅模式。文章首先举一个发布-订阅模式的样例,然后讲解了发布-订阅四个模式的原理,及对发布-订阅模式所依赖的观察者模式进行了举例,最后引出该模式在Springboot中的大量应用。

照例放一份refresh()方法的源码,registerListeners()方法位于该方法的第七个位置。

@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//1、刷新前的准备
prepareRefresh(); // Tell the subclass to refresh the internal bean factory.
//2、将会初始化 BeanFactory、加载 Bean、注册 Bean
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context.
//3、设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
prepareBeanFactory(beanFactory); try {
//4、模板方法
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context.
//执行BeanFactory后置处理器
invokeBeanFactoryPostProcessors(beanFactory); // 5、Register bean processors that intercept bean creation.
//注册bean后置处理器
registerBeanPostProcessors(beanFactory); // Initialize message source for this context.
//国际化
initMessageSource(); // Initialize event multicaster for this context.
initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses.
//6、模板方法--springboot实现了这个方法
onRefresh(); // Check for listener beans and register them.
//7、注册监听器
registerListeners(); // Instantiate all remaining (non-lazy-init) singletons.
//8、完成bean工厂的初始化**方法重要**********************************************
finishBeanFactoryInitialization(beanFactory); //9、 Last step: publish corresponding event.
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
} // Destroy already created singletons to avoid dangling resources.
destroyBeans(); // Reset 'active' flag.
cancelRefresh(ex); // Propagate exception to caller.
throw ex;
}

先大致看一下registerListeners()方法的源码。

protected void registerListeners() {
// Register statically specified listeners first.
// 首先注册静态的指定的监听器,注册的是特殊的事件监听器,而不是配置中的bean
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
} // Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let post-processors apply to them!
// 这里不会初始化FactoryBean,我们需要保留所有的普通bean
// 不会实例化这些bean,让后置处理器可以感知到它们
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
} // Publish early application events now that we finally have a multicaster...
// 现在有了事件广播组,发布之前的应用事件
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}

这个方法要做的很简单,就是两个循环遍历,把Spring通过硬编码定义的监听器注册到容器中,然后把我们自定义的监听器注册到容器中,通过这些直接叙述有一些苍白无力,我们写一个简单的发布-订阅的例子方便理解。

在发布订阅模式用需要四个角色:

ApplicationEvent:事件,每个实现类表示一类事件,可携带数据。抽象类。

ApplicationListener:事件监听器,用于接收事件处理时间。接口。

ApplicationEventMulticaster:事件管理者,可以注册(添加)/移除/发布事件。用于事件监听器的注册和事件的广播。接口。

ApplicationEventPublisher:事件发布者,委托事件管理者ApplicationEventMulticaster完成事件发布。

事件:

@Component
public class MyEvent extends ApplicationEvent { private static final long serialVersionUID = 1L; /**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public MyEvent(Object source) {
super(source);
}
}

事件监听器:

@Component
public class MyEventListener implements ApplicationListener<MyEvent> { @EventListener //@EventListener注解实现事件监听
@Override
public void onApplicationEvent(MyEvent event) {
Object msg = event.getSource();
System.out.println("自定义事件监听器(MyEventListener1)收到发布的消息: " + msg); }
}

事件发布者:

public static void main(String[] args) {
System.out.println(1);
ApplicationContext ac =new AnnotationConfigApplicationContext(MyEventListener.class);
MyEvent myEvent=new MyEvent(new Object());
ac.publishEvent(myEvent); }

那么事件的管理器跑哪去了呢?

我们在 registerListeners()方法上面打一个断点,看看我们自定义的事件是如何在Spring中起作用的。

第一个循环是Spring默认的监听器默认情况下是空。

我们自定义的事件监听器在第二个循环里面被加载到了ApplicationEventMulticaster中,已经很明显了,ApplicationEventMulticaster就是我们的事件管理者。

我们在把他们之间的关系详细串一下。

注:广播器和上面的管理者是一个意思

到这个阶段,事件管理者和监听器都在Spring容器里初始化和注册了,之后就可以实现监听者模式了,对事件的发布进行监听然后处理。

在就是ac.publishEvent(myEvent);方法发布事件后进行的业务处理。

ApplicationContext ac =new AnnotationConfigApplicationContext(MyEventListener.class);
MyEvent myEvent=new MyEvent(new Object());
ac.publishEvent(myEvent);

事件监听机制实际就是主题-订阅模式(观察者模式)的实现,能够降低代码耦合。

本文顺便把观察者模式简要的叙述一下,方便读者可以更好的理解。

观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

public class RMBrateTest {
public static void main(String[] args) {
Rate rate = new RMBrate();
Company watcher1 = new ImportCompany();
Company watcher2 = new ExportCompany();
rate.add(watcher1);
rate.add(watcher2);
rate.change(10);
rate.change(-9);
}
}
//抽象目标:汇率
abstract class Rate {
protected List<Company> companys = new ArrayList<Company>();
//增加观察者方法
public void add(Company company) {
companys.add(company);
}
//删除观察者方法
public void remove(Company company) {
companys.remove(company);
}
public abstract void change(int number);
}
//具体目标:人民币汇率
class RMBrate extends Rate {
public void change(int number) {
for (Company obs : companys) {
((Company) obs).response(number);
}
}
}
//抽象观察者:公司
interface Company {
void response(int number);
}
//具体观察者1:进口公司
class ImportCompany implements Company {
public void response(int number) {
if (number > 0) {
System.out.println("人民币汇率升值" + number + "个基点,降低了进口产品成本,提升了进口公司利润率。");
} else if (number < 0) {
System.out.println("人民币汇率贬值" + (-number) + "个基点,提升了进口产品成本,降低了进口公司利润率。");
}
}
}
//具体观察者2:出口公司
class ExportCompany implements Company {
public void response(int number) {
if (number > 0) {
System.out.println("人民币汇率升值" + number + "个基点,降低了出口产品收入,降低了出口公司的销售利润率。");
} else if (number < 0) {
System.out.println("人民币汇率贬值" + (-number) + "个基点,提升了出口产品收入,提升了出口公司的销售利润率。");
}
}
}

汇率变化就是一个事件,当该事件发生时,它的观察者出口公司和进口公司就要做相应的变化。

书归正传重新回到registerListeners()方法中,这时所有的监听器都注册到了容器中,只等publishEvent()发布事件以后,就会执行我们监听器中的业务逻辑。

据说在Springboot中应用了大量的发布订阅模式,抱着求知若渴的态度我们去Springboot中搂一眼,在registerListeners()上面打一个断点,看看和Spring上面的断点有什么区别。

在Spirng中监听器默认是空,当时在Springboot中有15个之多,不愧是加强版的Spring。具体这些监听器是干嘛的我们就不深究了,在Springboot中会对其逐步拆解的。

好啦,今天对registerListeners()的方法的剖析也就结束啦。

Spring源码之七registerListeners()及发布订阅模式的更多相关文章

  1. Spring Data Redis实现消息队列——发布/订阅模式

    一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式.利用redis这两种场景的消息队列都能够实现. 定义:生产者消费者模式:生产者生产消息放到队列里,多个消费者同时监听队列, ...

  2. Spring源码解析-Advice中的Adapter模式

    在spring中与通知相关的类有: 以Advice结尾的通知接口    MethodBeforeAdvice    AfterReturningAdvice   ThrowsAdvice 以Inter ...

  3. Spring Boot中使用redis的发布/订阅模式

    原文:https://www.cnblogs.com/meetzy/p/7986956.html redis不仅是一个非常强大的非关系型数据库,它同时还拥有消息中间件的pub/sub功能,在sprin ...

  4. Spring 源码(8)Spring BeanPostProcessor的注册、国际化及事件发布机制

    上一篇文章https://www.cnblogs.com/redwinter/p/16198942.html介绍了Spring的注解的解析过程以及Spring Boot自动装配的原理,大概回顾下:Sp ...

  5. Spring源码情操陶冶-AbstractApplicationContext#registerListeners

    承接前文Spring源码情操陶冶-AbstractApplicationContext#onRefresh 约定web.xml配置的contextClass为默认值XmlWebApplicationC ...

  6. 从发布订阅模式入手读懂Node.js的EventEmitter源码

    前面一篇文章setTimeout和setImmediate到底谁先执行,本文让你彻底理解Event Loop详细讲解了浏览器和Node.js的异步API及其底层原理Event Loop.本文会讲一下不 ...

  7. Spring源码分析之IOC的三种常见用法及源码实现(二)

    Spring源码分析之IOC的三种常见用法及源码实现(二) 回顾上文 我们研究的是 AnnotationConfigApplicationContext annotationConfigApplica ...

  8. spring源码分析系列 (15) 设计模式解析

    spring是目前使用最为广泛的Java框架之一.虽然spring最为核心是IOC和AOP,其中代码实现中很多设计模式得以应用,代码看起来简洁流畅,在日常的软件设计中很值得借鉴.以下是对一些设计模式的 ...

  9. Spring源码-IOC部分-容器初始化过程【2】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

随机推荐

  1. 题解 - 「MLOI」小兔叽

    小兔叽 \(\texttt{Link}\) 简单题意 有 \(n\) 个小木桩排成一行,第 \(i\) 个小木桩的高度为 \(h_i\),分数为 \(c_i\). 如果一只小兔叽在第 \(i\) 个小 ...

  2. Python初学笔记之字符串

    一.字符串的定义 字符串是就一堆字符,可以使用""(双引号).''(单引号)来创建. 1 one_str = "定义字符串" 字符串内容中包含引号时,可以使用转 ...

  3. mybatis 配置文件 简单介绍

    配置文件? 一. properties属性:可以引入外部的属性文件,可以自定义属性信息,供后续的节点使用: 注意!外部文件 定义在SRC目录下 否则找不到资源.二. settings设置:全局的配置信 ...

  4. 计算机网络再次整理————tcp的关闭[七]

    前言 tcp的关闭不是简单粗暴的,相对而言是友好优雅的,好聚好散吧. 那么友好的关闭方式是这样的: 假设这里是客户端请求关闭的,服务端倒过来. 客户端:我要请求关闭 服务端:我接收到你的请求了,等我把 ...

  5. 【然天一】随机读写(4k)百盘天梯

    随机读写适用于大量小文件的读写,是最贴近办公和编程的使用场景.现在很多硬盘厂商只宣传它们的连续读写(Seq),但除了游戏和视频剪辑场景之外并没有什么卵用. 总结一下: 傲腾秒杀全部 NAND SLC ...

  6. ApacheCN 大数据译文集(二) 20211206 更新

    Hadoop3 大数据分析 零.前言 一.Hadoop 简介 二.大数据分析概述 三.MapReduce 大数据处理 四.基于 Python 和 Hadoop 的科学计算和大数据分析 五.基于 R 和 ...

  7. document对象常用属性

    转载请注明来源:https://www.cnblogs.com/hookjc/ 注:页面上元素name属性和JavaScript引用的名称必须一致包括大小写    否则会提示你一个错误信息 " ...

  8. C语言之清空缓存区

    感谢大佬:https://blog.csdn.net/qq_26768741/article/details/50933598 在C语言中,我们常常需要去清空缓存区,对于缓存区清空的重要性,接下来我们 ...

  9. linux内核可以接受的参数 | Linux kernel启动参数 | 通过grub给内核传递参数

    在Linux中,给kernel传递参数以控制其行为总共有三种方法: 1.build kernel之时的各个configuration选项. 2.当kernel启动之时,可以参数在kernel被GRUB ...

  10. 简述redis特点及其应用场景

    1. Redis八大特点 1.1. 速度快 说到Redis的速度快,大家的第一反应一定是内存读取,那是肯定的,但如果面试的时候仅仅说到这点,那还是远远不够的,至少还有以下三点要补充: Redis是用C ...