Spring的事件监听机制

news2024/11/26 23:38:39

这里写自定义目录标题

  • 1. 概述(重点)
  • 2. ApplicationEventMulticaster
    • 2.1 SimpleApplicationEventMulticaster
    • 2.2 AbstractApplicationEventMulticaster
  • 3. ApplicationListener
    • 3.1 注册监听器
    • 3.2 自定义
  • 4. SpringApplicationRunListeners

1. 概述(重点)

事件监听机制是观察者模式的一种,Spring的事件监听机制有几个重要的顶层接口,分别是:

  1. ApplicationEventMulticaster 主要看 AbstractApplicationEventMulticaster
  2. ApplicationListener
  3. ApplicationEvent

它们三哥们的关系可以用下面的图来概括

在这里插入图片描述
AbstractApplicationEventMulticaster维护了 源类型和事件类型 作为KEY 跟 监听器的关系,在下面 2.1 SimpleApplicationEventMulticaster有代码的实现解读。

2. ApplicationEventMulticaster

AbstractApplicationEventMulticaster
«Interface»
ApplicationEventMulticaster
«Interface»
Aware
«Interface»
BeanClassLoaderAware
«Interface»
BeanFactoryAware
SimpleApplicationEventMulticaster

目前就只有一个实现类SimpleApplicationEventMulticaster和一个抽象类AbstractApplicationEventMulticaster

2.1 SimpleApplicationEventMulticaster

发布和广播事件,主要注意一下getApplicationListeners方法就行了,会根据事件类型源类型找监听器。

@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);
        }
    }
}

2.2 AbstractApplicationEventMulticaster

getApplicationListeners方法就是根据Class<?>、ResolvableType两个对象作为一个缓存key,这个key与一堆监听器`ApplicationListener`做映射,也就是说通过Class<?>、ResolvableType两个对象可以找到监听器,在用法上可以从SimpleApplicationEventMuticastermulticastEvent 方法去看。

看看getApplicationListeners方法的代码实现

protected Collection<ApplicationListener<?>> getApplicationListeners(
	    ApplicationEvent event, ResolvableType eventType) {

    Object source = event.getSource();
    Class<?> sourceType = (source != null ? source.getClass() : null);
    ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
    CachedListenerRetriever newRetriever = null;

    // 这个cacheKey就是ApplicationEvent,ResolvableType两个对象。
    // 根据cacheKey找监听器,找不到就把cacheKey作为key,创建一个什么都么有的CachedListenerRetriever对象作为值缓存到retrieverCache中。
    // 这里有一些细节我没搞明白,putIfAbsent总是返回null。
    CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);
    if (existingRetriever == null) {
        if (this.beanClassLoader == null ||
            (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
            (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
            newRetriever = new CachedListenerRetriever();
            existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
            if (existingRetriever != null) {
                newRetriever = null;  // no need to populate it in retrieveApplicationListeners
            }
        }
    }

    if (existingRetriever != null) {
        Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
        if (result != null) {
            return result;
        }
    }

    // 假设上面cacheKey对应的值没有监听器,那这个方面就找 给定事件类型和源类型 的监听器。
    return retrieveApplicationListeners(eventType, sourceType, newRetriever);
}

// 从默认的 defaultRetriever 和 我们注入的bean 中找监听器。
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
        ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {

    List<ApplicationListener<?>> allListeners = new ArrayList<>();
    Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);
    Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);

    Set<ApplicationListener<?>> listeners;
    Set<String> listenerBeans;
    synchronized (this.defaultRetriever) {
    listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
    listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
    }

    // 从defaultRetriever中找监听器
    // 这里的supportsEvent有点看不懂,不过只要理解通过事件类型和源类型判断监听器是否符合 事件要求就得了。
    for (ApplicationListener<?> listener : listeners) {
        if (supportsEvent(listener, eventType, sourceType)) {
            if (retriever != null) {
                filteredListeners.add(listener);
            }
            allListeners.add(listener);
        }
    }

    // 根据bean名字去找监听器
    if (!listenerBeans.isEmpty()) {
        ConfigurableBeanFactory beanFactory = getBeanFactory();
        for (String listenerBeanName : listenerBeans) {
            try {
                if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
                    ApplicationListener<?> listener =
                        beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                    if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
                        if (retriever != null) {
                            if (beanFactory.isSingleton(listenerBeanName)) {
                                filteredListeners.add(listener);
                            }
                            else {
                                filteredListenerBeans.add(listenerBeanName);
                            }
                        }
                        allListeners.add(listener);
                    }
                }
                else {
                    Object listener = beanFactory.getSingleton(listenerBeanName);
                    if (retriever != null) {
                        filteredListeners.remove(listener);
                    }
                    allListeners.remove(listener);
                }
                }
            catch (NoSuchBeanDefinitionException ex) {
            }
        }
    }

    // 把找到的监听器都放到CachedListenerRetriever中。
    AnnotationAwareOrderComparator.sort(allListeners);
    if (retriever != null) {
        if (filteredListenerBeans.isEmpty()) {
            retriever.applicationListeners = new LinkedHashSet<>(allListeners);
            retriever.applicationListenerBeans = filteredListenerBeans;
        }
        else {
            retriever.applicationListeners = filteredListeners;
            retriever.applicationListenerBeans = filteredListenerBeans;
        }
    }
    return allListeners;
}

3. ApplicationListener

主要是了解如何注册和自定义监听器就行了

3.1 注册监听器

通过META-INF/spring.factories注册监听器。

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();
    this.bootstrapRegistryInitializers = new ArrayList<>(
        getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 这里就是注册ApplicationListener的代码了,通过META-INF/spring.factories注册。
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

3.2 自定义

public class MyApplicationPreparedListener implements ApplicationListener<ApplicationPreparedEvent> {
    @Override
    public void onApplicationEvent(ApplicationPreparedEvent event) {
        System.out.println("开始填充上下文。。。。");
    }
}

MATE-INF/spring.factories

org.springframework.context.ApplicationListener=sample.config.MyApplicationPreparedListener

4. SpringApplicationRunListeners

SpringApplicationRunListeners管理了多个EventPublishingRunListener,EventPublishingRunListener里面包含了事件监听器模型#1.概述(重点)中描述的部分。

class SpringApplicationRunListeners {

    private final Log log;

    private final List<SpringApplicationRunListener> listeners;

    private final ApplicationStartup applicationStartup;

    ...
}

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

    private final SpringApplication application;

    private final String[] args;

    private final SimpleApplicationEventMulticaster initialMulticaster;

    ...
}

EventPublishingRunListener中定义了Spring整个启动过程中会触发的事件,下面对触发位置进行大概的描述,更具体的内容还有待研究。

  • starting
    在SpringApplication#run,事件ApplicationStartingEvent
  • environmentPrepared
    在SpringApplication#prepareEnvironment,事件ApplicationEnvironmentPreparedEvent
  • contextPrepared
    在SpringApplication#prepareContext,事件ApplicationContextInitializedEvent
  • contextLoaded
    在SpringApplication#prepareContext,事件ApplicationPreparedEvent
  • started
    在SpringApplication#run,事件ApplicationStartedEvent
  • ready
    在SpringApplication#run,事件ApplicationReadyEvent
  • failed
    在SpringApplication#handleRunFailure,事件ApplicationFailedEvent

我们也可以利用上面的事件,自己创建一个监听器,然后放到spring.factories,比如现在注册一个ApplicationPreparedEvent的监听器。

public class MyApplicationPreparedListener implements ApplicationListener<ApplicationPreparedEvent> {
    @Override
    public void onApplicationEvent(ApplicationPreparedEvent event) {
        System.out.println("开始填充上下文。。。。");
    }
}

MATE-INF/spring.factories

org.springframework.context.ApplicationListener=sample.config.MyApplicationPreparedListener

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

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

相关文章

气膜厂家产品种类繁多,哪种适合您?

气膜是一种以薄膜为材料、通过气体充气而形成的充气结构。由于其轻便、灵活、耐用等优点&#xff0c;在各个领域都有广泛应用。气膜厂家生产的产品种类繁多&#xff0c;下面将介绍几种常见的气膜产品&#xff0c;并分析哪种适合您。 气膜建筑是气膜厂家的特色产品之一。气膜建…

探析零知识证明高能发展路径:走向更安全、私密且可扩展的 Web3 新时代

原文&#xff1a;https://www.coinbase.com/blog/understanding-the-zero-knowledge-landscape 作者&#xff1a;Jonathan King&#xff5c;Coinbase Ventures 编译&#xff1a;TinTinLand 本文核心观点 2023 年&#xff0c;零知识技术吸引了逾 4 亿美元的投资&#xff0c;主…

凝聚共识开新篇:产业“围炉谈”共促5G-A加速

由北京通信学会主办的“新阶段、新体验、新价值”产业围炉谈活动在北京时间1月25日已成功举办。 来自社会各界的专家代表齐聚一堂&#xff0c;围炉畅谈5G-A产业发展&#xff0c;共同呼吁5G-A产业加速&#xff0c;擘画数字发展新画卷。 承前启后&#xff0c;5G-A开启5G新阶段 …

MySQL索引的原理和SQL优化策略

1. 索引 在InnoDB存储引擎中&#xff0c;索引分为聚簇索引和辅助索引两种类型。 聚簇索引是指基于表的主键构建的索引&#xff0c;它决定了表中数据的物理存储顺序。也就是说&#xff0c;聚簇索引中的键值按照主键的顺序来排序&#xff0c;并且每个叶子节点存储的是整个表行的…

VBA技术资料MF113:将文件夹图像添加到PowerPoint

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。我的教程一共九套&#xff0c;分为初级、中级、高级三大部分。是对VBA的系统讲解&#xff0c;从简单的入门&#xff0c;到…

小程序跳转:云开发之h5跳小程序

背景&#xff1a; 抖音通过链接跳转微信小程序。考虑使用h5页面中转实现&#xff0c;下面是实现步骤。 官方的文档上面写的还是比较详细的&#xff0c;可以仔细阅读&#xff0c;按照步骤去操作。 实践总结&#xff1a; 测试必须使用真机测试&#xff0c;模拟器之类的全部不…

Spring实现事务(一)

Spring事务 .什么是事务事务的操作Spring中事务的实现准备工作创建表创建项目,引入Spring Web, Mybatis, mysql等依赖配置文件实体类 编程式事务(手动写代码操作事务)声明式事务(利用注解自动开启和提交事务) . 什么是事务 事务是⼀组操作的集合, 是⼀个不可分割的操作 在我们…

P9809 [SHOI2006] 作业 Homework 浅显易懂讲解这道题为什么根号分治

题目&#xff1a; 我们有一堆数&#xff0c;找出模Y的最小值。 思路&#xff1a; 我们初步思考&#xff0c;会发现每个Y是一段&#xff0c;比如 1~Y , Y~2Y , 2Y~3Y ... 每个区间都可能有最小的答案。 这里对Y可以使用根号分治&#xff0c;因为&#xff1a; 当Y足够大时&a…

MySQL原理(一)架构组成之物理文件组成

目录 一、日志文件 1、错误日志 Error Log 1.1、作用&#xff1a; 1.2、开启关闭&#xff1a; 1.3、使用 2、二进制日志 Binary Log & Binary Log Index 2.1、作用&#xff1a; 2.2、开启关闭&#xff1a; 2.3、Binlog还有一些附加选项参数 &#xff08;1&#x…

8.4 Springboot整合Redis 之RedisTemplate方式

文章目录 前言一、Maven依赖二、配置文件application.properties2.1 连接池核心配置说明三、RedisTemplate配置类四、RedisTemplate工具类五、测试前言 上文我们讲解了官方推荐的Jedis方式,本文讲解Springboot通过Spring Data Redis 集成 Redis,主要使用RedisTemplate方式,…

LeetCode 使循环数组所有元素相等的最少秒数

地址&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 难度&#xff1a;中等 题目描述&#xff1a;给你一个下标从 0 开始长度为 n 的数组 nums 。 每一秒&#xff0c;你可以对数组执行以下操作&#xff1a; 对于范围在 [0, n - 1] 内的每…

Java 类的加载流程

一、类的加载 指的是将类的.class 文件中的二进制 数据读入到内存中&#xff0c;将其放在运行时数据区的方法区内&#xff0c;然后在堆区创 建一个 java.lang.Class 对象&#xff0c;用来封装类在方法区内的数据结构。 类从被加载到虚拟机内存中开始&#xff0c;到卸载出内…

Android super.img解包和打包指南(含工具下载lpunpack、lpmake、lpdump)

本文所有命令均需要在linux 上执行 一、解包 1、将Android sparse image格式的super.img转成二进制文件 $ sudo apt install android-sdk-libsparse-utils $ simg2img super.img super.img.bin 2、下载工具lpunpack 和lpmake、lpdump 以及其依赖库 下载地址:https://downl…

进程控制(二)进程等待

文章目录 进程等待什么是进程等待&#xff1f;&#xff1f;&#xff1f;为什么要进行进程等待&#xff1f;&#xff1f;&#xff1f; 进程等待的方法wait函数waitpid函数 进程等待 什么是进程等待&#xff1f;&#xff1f;&#xff1f; 进程等待是通过wait/waitpid的方式&…

异或运算实现加密解密

异或运算符^&#xff0c;相同为0&#xff0c;不同为1&#xff08;同0非1&#xff09; 由异或运算法则可知&#xff1a;a ^ a 0&#xff0c;a ^ 0 a 如果c a ^ b&#xff0c;那么a b ^ c&#xff0c;即a ^ b ^ b a&#xff0c;^ 的逆运算仍然是 ^ 利用异或运算的性质&am…

python封装的.exe文件是如何在cmd中获取.xml路径的?

这段日子搞项目算法封装&#xff0c;愁死我。来回改了三遍&#xff0c;总算把相对路径、绝对路径&#xff0c;还有cmd给.exe传参的方式搞懂了。 主要是这个语句 workspace sys.argv[1] sys.argv[]的作用就是,在运行python文件的时候从外部输入参数往文件里面传递参数。 外部就…

09. 配置Eth-Trunk

文章目录 一. 初识Eth-Trunk1.1. Eth-Trunk的概述1.2. Eth-Trunk的优势1.3. Eth-Trunk的模式的优势 二. 实验专题2.1. 实验1&#xff1a;手工模式2.1.1. 实验拓扑图2.1.2. 实验步骤&#xff08;1&#xff09;配置PC机的IP地址&#xff08;2&#xff09;在交换机接口划入VLAN&am…

oracle 21C RAC+linux 8安装配置手册

本文详细介绍利用虚拟机安装配置oracle 21c rac OS版本&#xff1a;oracle linux 8.4 oracle版本&#xff1a;21.3 博主文章目前只发布在两个平台CSDN和墨天伦 ID&#xff1a;潇湘秦&#xff0c;转载请注明出处 安装oracle linux 8.4 选择本地可用的ISO 文件&#xff08;虚拟…

AI Prompt工程师 学习整理

前言 如果说Al大语言模型(LLM,Large Language Model)是宝藏我,那么Prompt提示词就是打开宝藏的钥匙。 最新一代的Al大语言模型具备出色的创作能力,能够生成富有人类感情、严谨逻辑、多场景应用的内容,而如何获得高质量的回答,正确学习使用Prompt提示词是关键。 &#x1f4a5…

vue-cli项目运行流程介绍

一、前言 ​ 本文介绍 vue-cli搭建的项目运行流程&#xff0c;基于已经搭建好的基础项目。关于 vue-cli 构建项目的详细流程&#xff0c;可参考博文&#xff1a;使用vue脚手架构建项目 二、main.js 项目运行 会加载入口文件 main.js /* html文件中&#xff0c;通过script …