https://www.bilibili.com/video/BV12C4y1s7dR?p=11

监听器模式要素

  • 事件
  • 监听器
  • 广播器
  • 触发机制

Springboot中监听模式总结

  1. 在SpringApplication初始化中从META_INF/spring.factories获取Listeners
  2. 创建监听器
  3. 创建广播器
  4. 将监听器在广播器中进行注册
  5. 事件触发时,监听器监听到,同时将创建的事件传给广播器,执行广播器,通过回调调用实际事件中方法

Springboot中监听器各要素

事件
public abstract class ApplicationEvent extends EventObject {

	/** use serialVersionUID from Spring 1.2 for interoperability. */
private static final long serialVersionUID = 7099057708183571937L; /** System time when the event happened. */
private final long timestamp; /**
* Create a new {@code ApplicationEvent}.
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
} /**
* Return the system time in milliseconds when the event occurred.
*/
public final long getTimestamp() {
return this.timestamp;
} }
监听器
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { /**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event); }
广播器
public interface ApplicationEventMulticaster {
void addApplicationListener(ApplicationListener<?> listener);
void addApplicationListenerBean(String listenerBeanName);
void removeApplicationListener(ApplicationListener<?> listener);
void removeApplicationListenerBean(String listenerBeanName);
void removeAllListeners();
void multicastEvent(ApplicationEvent event);
void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
}
事件触发,如:

SpringAppliation中listeners.starting();

源码梳理

在SpringApplication初始化中setListeners

在SpringApplication构造函数中,执行setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}

执行SpringFactoriesLoader类中loadSpringFactories方法,将jar包中所有META_INF/spring.factories文件中的属性进行加载(在setInitializers中已经加载,所以在setListeners直接从MultiValueMap缓存中取)

Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

调用链:

SpringApplication#init() --> SpringApplication#getSpringFactoriesInstances() --> SpringFactoriesLoader#loadFactoryNames() --> SpringFactoriesLoader#loadSpringFactories()

loadSpringFactories代码:

	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
} try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}

调用createSpringFactoriesInstances方法进行实例化

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args, Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}

将获取到的listeners注册到List<ApplicationListener<?>> listeners中

public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>(listeners);
}
获取SpringApplicationRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

key为org.springframework.boot.SpringApplicationRunListener,从之前缓存的map中取出SpringApplicationRunListener,value为EventPublishingRunListener

对EventPublishingRunListener进行初始化,创建SimpleApplicationEventMulticaster广播器

public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}

将监听器注册到广播器中:

for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
事件触发

SpringApplication中listeners.starting();

执行到EventPublishingRunListener中

@Override
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}

进行广播事件:

@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
} @Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}

然后去调用具体事件的onApplicationEvent方法

Spring源码之Springboot中监听器介绍的更多相关文章

  1. Spring源码之AbstractApplicationContext中refresh方法注释

    https://blog.csdn.net/three_stand/article/details/80680004 refresh() prepareRefresh(beanFactory) 容器状 ...

  2. Spring源码系列(一)--详解介绍bean组件

    简介 spring-bean 组件是 IoC 的核心,我们可以通过BeanFactory来获取所需的对象,对象的实例化.属性装配和初始化都可以交给 spring 来管理. 针对 spring-bean ...

  3. Mybatis源码解读-SpringBoot中配置加载和Mapper的生成

    本文mybatis-spring-boot探讨在springboot工程中mybatis相关对象的注册与加载. 建议先了解mybatis在spring中的使用和springboot自动装载机制,再看此 ...

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

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

  5. spring源码学习之路---AOP初探(六)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近工作很忙,但当初打算学习 ...

  6. spring源码学习之bean的加载(一)

    对XML文件的解析基本上已经大致的走了一遍,虽然没有能吸收多少,但是脑子中总是有些印象的,接下来看下spring中的bean的加载,这个比xml解析复杂的多.这个加载,在我们使用的时候基本上是:Bea ...

  7. spring源码学习(一)--AOP初探

    LZ以前一直觉得,学习spring源码,起码要把人家的代码整体上通读一遍,现在想想这是很愚蠢的,spring作为一个应用平台,不是那么好研究透彻的,而且也不太可能有人把spring的源码全部清楚的过上 ...

  8. Spring源码分析-从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(一)?

    阅前提醒 全文较长,建议沉下心来慢慢阅读,最好是打开Idea,点开Spring源码,跟着下文一步一步阅读,更加便于理解.由于笔者水平优先,编写时间仓促,文中难免会出现一些错误或者不准确的地方,恳请各位 ...

  9. Spring源码分析(十四)从bean的实例中获取对象

    摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 在getBean方法中,getObjectForBeanlnstance ...

随机推荐

  1. day56 Pyhton 前端Jquery08

    前端 内容回顾: -BOM -jquery介绍 -jquery下载和引入方式 npm install jquery -jquery的选择器 -基本选择器 -通配符选择器 - id选择器 - 类选择器 ...

  2. CentOS 7的安装与部署 01

    01 虚拟软件的安装与配置 虚拟机(Virtual Machine)指通过软件模拟的具有完整硬件系统功能的.运行在一个完全隔离环境中的完整计算机系统.在实体计算机中能够完成的工作在虚拟机中都能够实现. ...

  3. Python列表的增删改查

    列表的增: li = ['libai','sushi','dufu','sushi',"白居易"] 第一种: append():向列表末尾追加元素 li.append('diaoc ...

  4. MySQL备份和恢复[1]-概述

    备份类型 完全备份,部分备份 完全备份:整个数据集 部分备份:只备份数据子集,如部分库或表 完全备份.增量备份.差异备份 增量备份:仅备份最近一次完全备份或增量备份(如果存在增量)以来变化的数据,备份 ...

  5. 2. A Distributional Perspective on Reinforcement Learning

    本文主要研究了分布式强化学习,利用价值分布(value distribution)的思想,求出回报\(Z\)的概率分布,从而取代期望值(即\(Q\)值). Q-Learning Q-Learning的 ...

  6. 一文带你趟过mac搭建appium测试环境的遇到的坑

    做UI自动化,最难的一步就是在环境搭建上,怎么去搭建一个UI自动化测试的环境,会难住很多人,在Mac上搭建appium如何搭建呢,本文带着大家去领略如何在mac上搭建appium测试环境.下面就是详细 ...

  7. docker-compose启动consul集群

    version: '2.0' services: consul-server1: image: consul:latest hostname: "consul-server1" p ...

  8. 续 Eureka+Hystrix+Zuul(Zuul API网关)

    概述: 服务的合并,转发,验证,异构协议.将上百个请求融为一个发送到后台,返回大的JSON返回处理: 作用:这减少了客户端与应用程序间的交互次数,还简化了客户端代码. Zuul可以通过加载动态过滤机制 ...

  9. ViewModel和LiveData问题思考与解答

    嗨,大家好,面试真题系列又来了,今天我们说说MVVM架构里的两大组件:ViewModel和LiveData. 还是老样子,提出问题,做出解答. ViewModel 是什么? ViewModel 为什么 ...

  10. 04 . Go+Vue开发一个线上外卖应用(用户名密码和图形验证码)

    图形化验证码生成和验证 功能介绍 在使用用户名和密码登录功能时,需要填写验证码,验证码是以图形化的方式进行获取和展示的. 验证码使用原理 验证码的使用流程和原理为:在服务器端负责生成图形化验证码,并以 ...