[源码系列:手写spring] IOC第十四节:容器事件和事件监听器

news2025/1/17 5:50:50

代码分支

https://github.com/yihuiaa/little-spring/tree/event-and-event-listenericon-default.png?t=N7T8https://github.com/yihuiaa/little-spring/tree/event-and-event-listener

内容介绍

事件监听器机制

        Spring的容器事件和事件监听器机制允许应用程序在容器中发生特定事件时执行自定义逻辑。这是一种基于观察者模式的设计,其中容器充当主题,而事件监听器则充当观察者。

  1. 事件(Event):事件是表示应用程序中某个特定事件的对象,通常扩展自ApplicationEvent类。您可以创建自定义事件来表示您关心的特定事件。

  2. 事件监听器(Event Listener):事件监听器是一个实现了ApplicationListener接口的Bean,它负责处理特定事件。监听器可以注册到容器中以接收特定类型的事件,并在事件发生时执行相应的操作。

  3. ApplicationEventMulticaster:这是Spring事件传播机制的关键部分,它负责将事件传播给注册的监听器。Spring提供了不同的ApplicationEventMulticaster实现,最常见的是SimpleApplicationEventMulticasterAsyncApplicationEventMulticaster

  4. AbstractApplicationContextAbstractApplicationContext是Spring上下文的抽象基类,它包括了事件发布和监听的功能。

实现原理

  • ApplicationEventMulticasterApplicationEventMulticaster的主要职责是管理事件监听器以及在事件发生时通知它们。它通常是Spring应用程序上下文的一部分。

    • SimpleApplicationEventMulticaster这是最简单的ApplicationEventMulticaster实现,它将事件同步传播给所有已注册的监听器。在事件发布时,它遍历注册的监听器列表,并调用每个监听器的onApplicationEvent方法。

    • AsyncApplicationEventMulticasterSimpleApplicationEventMulticaster不同,AsyncApplicationEventMulticaster可以异步地传播事件。它使用线程池来处理事件监听器的通知,因此不会阻塞主线程。

  • AbstractApplicationContextAbstractApplicationContext是Spring上下文的抽象基类,它包括了事件发布和监听的功能。它具有以下主要方法:

    • publishEvent(ApplicationEvent event)用于发布事件。它会将事件传递给ApplicationEventMulticaster以进行分发。

    • addApplicationListener(ApplicationListener<?> listener)用于向容器中注册事件监听器。

    • refresh()通常在容器启动时调用,用于初始化上下文和注册所有默认的事件监听器。实例化ApplicationEventMulticaster、注册监听器并发布容器刷新事件ContextRefreshedEvent。

    • doClose() : 发布容器关闭事件ContextClosedEvent。

知识补充

观察者模式

        察言观色、思考分析一直是人类认识客观事物的重要途径。观察行为通常是一种为了对目标状态变化做出及时响应而采取的监控及调查活动。观察者模式(Observer)可以针对被观察对象与观察者对象之间一对多的依赖关系建立起一种行为自动触发机制,当被观察对象状态发生变化时主动对外发起广播,以通知所有观察者做出响应。观察者往往眼观六路,耳听八方,随时监控着被观察对象的一举一动。作为主动方的观察者对象必须与被观察对象建立依赖关系,以获得其最新动态,例如记者与新闻、摄影师与景物、护士与病人、股民与股市等

        现实中的观察者(Observer)往往是主动方,这是由于目标主题(Subject)缺乏主观能动性造成的,其状态的更新并不能主动地通知观察者,这就造成观察行为的持续往复。而在软件设计中我们可以将目标主题作为主动方角色,将观察者反转为被动方角色,建立反向驱动式的消息响应机制,以此来避免做无用功,优化软件效率,请参看观察者模式的类结构,如图所示。

观察者模式的类结构

观察者模式的各角色定义如下。

■ Subject(目标主题):被观察的目标主题的接口抽象,维护观察者对象列表,并定义注册方法register()(订阅)与通知方法notify()(发布)。对应本章例程中的商店类Shop。

■ ConcreteSubject(主题实现):被观察的目标主题的具体实现类,持有一个属性状态State,可以有多种实现。对应本章例程中的商店类Shop。

■ Observer(观察者):观察者的接口抽象,定义响应方法update()。对应本章例程中的买家类Buyer。

■ ConcreteObserver(观察者实现):观察者的具体实现类,可以有任意多个子类实现。实现了响应方法update(),收到通知后进行自己独特的处理。对应本章例程中的手机买家类PhoneFans、海淘买家类HandChopper。 

 一对多关系

        基于这种一对多的关系网,观察者模式以多态化(泛型化)的方式弱化了目标主题与观察者之间强耦合的依赖关系,标准化它们的消息交互接口,并让主客关系发生反转,以“单方驱动全局”模式取代“多方持续轮询”模式,使目标主题(单方)的任何状态更新都能被即刻通过广播的形式通知观察者们(多方),解决了状态同步知悉的效率问题。

                                                                                                              ——《秒懂设计模式》        

案例

        现在,让我们提供一个使用案例来演示Spring容器事件和事件监听器的工作原理:

        假设你正在构建一个在线商店应用程序,并希望在用户下订单时发送邮件通知。首先,你需要创建一个自定义事件来表示订单的提交:

import org.springframework.context.ApplicationEvent;

public class OrderEvent extends ApplicationEvent {
    private String orderId;

    public OrderEvent(Object source, String orderId) {
        super(source);
        this.orderId = orderId;
    }

    public String getOrderId() {
        return orderId;
    }
}

接下来,需要创建一个事件监听器来处理订单事件并发送邮件通知:

import org.springframework.context.ApplicationListener;

public class EmailNotificationListener implements ApplicationListener<OrderEvent> {
    @Override
    public void onApplicationEvent(OrderEvent event) {
        // 在这里编写发送邮件通知的逻辑,可以使用JavaMail等库
        String orderId = event.getOrderId();
        System.out.println("Sending email notification for order: " + orderId);
    }
}

然后,在Spring配置文件中注册事件监听器:
 

<bean class="com.example.EmailNotificationListener" />

最后,在应用程序中发布订单事件:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class OrderApplication {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        
        // 模拟用户下订单
        String orderId = "12345";
        OrderEvent orderEvent = new OrderEvent(context, orderId);
        
        // 发布订单事件
        context.publishEvent(orderEvent);
    }
}

        当你运行OrderApplication时,它会发布订单事件,触发EmailNotificationListener中的邮件通知逻辑,从而实现了基于事件的订单通知功能。在真实场景下,可以创建一个专门的订单服务或业务逻辑组件,用于处理订单的创建和其他相关操作。在这个服务中注入ApplicationContext以获取事件发布的能力。

核心代码

ApplicationEvent(事件接口)

          继承了JDK自带的EventObject接口,source记录最初的事件源。

public abstract class ApplicationEvent extends EventObject {
	public ApplicationEvent(Object source) {
		super(source);
	}
}

ApplicationListener(监听器接口)

        继承了JDK自带的EventListener接口,ApplicationListener 接口是 Spring Framework 中的一个重要接口,用于监听应用程序中的事件,并在事件发生时执行特定的操作。ApplicationListener 接口包含一个方法 onApplicationEvent(ApplicationEvent event),该方法用于处理接收到的事件。

public interface ApplicationListener<E extends ApplicationEvent> {
    /**
     * 观察者的响应方法
     * @param event
     */
    void onApplicationEvent(E event);
}

ApplicationEventPublisher(事件发布者)

public interface ApplicationEventPublisher {

	/**
	 * 发布事件
	 *
	 * @param event
	 */
	void publishEvent(ApplicationEvent event);
}

ApplicationEventMulticaster(事件广播器接口)

包括添加事件监听器,删除时间监听器,广播事件的方法定义。

public interface ApplicationEventMulticaster {

	void addApplicationListener(ApplicationListener<?> listener);

	void removeApplicationListener(ApplicationListener<?> listener);

	void multicastEvent(ApplicationEvent event);

}

AbstractApplicationEventMulticaster(抽象广播器类实现通用方法和属性)

public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanFactoryAware {
    /**
     * 监听器集合,相当于观察者模式中的观察者列表
     */
    public final Set<ApplicationListener<org.springframework.context.ApplicationEvent>> applicationListeners = new HashSet<>();

    private BeanFactory beanFactory;
    @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override public void addApplicationListener(ApplicationListener<?> listener) {
        applicationListeners.add((ApplicationListener<org.springframework.context.ApplicationEvent>) listener);
    }

    @Override public void removeApplicationListener(ApplicationListener<?> listener) {
        applicationListeners.remove(listener);
    }
}

SimpleApplicationEventMulticaster(简单广播器实现类)

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster{

    /**
     * 创建一个 SimpleApplicationEventMulticaster 实例,并指定用于查找监听器的 BeanFactory。
     *
     * @param beanFactory 用于查找监听器的 BeanFactory
     */
    public SimpleApplicationEventMulticaster(BeanFactory beanFactory) {
        setBeanFactory(beanFactory);
    }

    /**
     * 广播给定的应用程序事件到所有注册的监听器。
     * @param event 要多播的应用程序事件
     */
    @Override public void multicastEvent(ApplicationEvent event) {
        for (ApplicationListener<org.springframework.context.ApplicationEvent> listener : applicationListeners) {
            if(supportsEvent(listener,event){
                listener.onApplicationEvent(event);
            }
        }
    }

    /**
     * 检查监听器是否对给定的事件感兴趣。
     *
     * @param applicationListener 要检查的监听器
     * @param event              要检查的事件
     * @return 如果监听器支持处理事件,则返回 true,否则返回 false
     */
    protected boolean supportsEvent(ApplicationListener<ApplicationEvent> applicationListener, ApplicationEvent event) {
        //适用于简单实例化策略SimpleInstantiationStrategy
        //todo 有兴趣的同学可以完善cglib的实例化方法加以适配
        //获取泛型接口
        Type genericInterfaceType = applicationListener.getClass().getGenericInterfaces()[0];
        // 获取泛型接口中的实际类型参数,也就是是事件的类型
        Type actualTypeArgument = ((ParameterizedType)genericInterfaceType).getActualTypeArguments()[0];


        //事件名称
        String eventClassName = actualTypeArgument.getTypeName();
        Class<?> eventClass;
            try {
                eventClass = Class.forName(eventClassName);
            }catch (ClassNotFoundException e) {
            throw new BeansException("wrong event class name: " + eventClassName);
        }
        // 检查事件是否是监听器支持的类型
        // 如果事件类是监听器泛型参数的子类或实现类,则返回 true,表示监听器支持处理此事件
        return eventClass.isAssignableFrom(event.getClass());
    }

}

ContextClosedEvent

表示当应用程序上下文(ApplicationContext)关闭时触发的事件。   

public class ContextClosedEvent extends ApplicationContextEvent {

	public ContextClosedEvent(ApplicationContext source) {
		super(source);
	}
}

 ContextRefreshedEvent

表示当应用程序上下文(ApplicationContext)成功刷新并初始化后触发的事件。

public class ContextRefreshedEvent extends ApplicationContextEvent {

	public ContextRefreshedEvent(ApplicationContext source) {
		super(source);
	}
}

ApplicationContext

添加发布事件的能力,继承发布者接口。

public interface ApplicationContext extends ListableBeanFactory, HierarchicalBeanFactory, ResourceLoader,ApplicationEventPublisher {
}

AbstractApplicationContext

修改刷新容器的方法,刷新容器时初始化事件广播器,注册事件监听器,发布容器刷新事件。

...    
@Override public void refresh() throws BeansException {
        //创建BeanFactory,并加载BeanDefinition
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();

        //添加ApplicationContextAwareProcessor,让继承自ApplicationContextAware的bean能感知bean
        beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));

        //执行BeanFactoryPostProcessor
        invokeBeanFactoryPostProcessors(beanFactory);
        //BeanPostProcessor的实例化提前与其他bean
        registerBeanPostProcessors(beanFactory);

        //初始化事件广播器
        initApplicationEventMulticaster();

        //注册事件监听器
        registerListeners();
        
        //提前实例化单例Bean
        beanFactory.preInstantiateSingletons();

        //发布容器刷新完成事件
        finishRefresh();
    }

    /**
     * 初始化事件广播器
     */
    private void initApplicationEventMulticaster(){
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.addSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME,applicationEventMulticaster);
    }

    /**
     * 注册事件监听器
     */
    private void registerListeners(){
        Collection<ApplicationListener> listeners = getBeansOfType(ApplicationListener.class).values();
        for(ApplicationListener listener : listeners){
            this.applicationEventMulticaster.addApplicationListener(listener);
        }

    }

    /**
     * 发布容器刷新完成事件
     */
    protected void finishRefresh() {
        publishEvent(new ContextRefreshedEvent(this));
    }

    @Override public void publishEvent(ApplicationEvent event) {
        applicationEventMulticaster.multicastEvent(event);
    }

    protected void doClose(){
        //发布容器关闭事件
        publishEvent(new ContextClosedEvent(this));
        destroyBeans();
    }


...

测试

测试代码

event-and-event-listener.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	         http://www.springframework.org/schema/beans/spring-beans.xsd
		 http://www.springframework.org/schema/context
		 http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<!--容器刷新监听器-->
    <bean class="common.event.ContextRefreshedEventListener"/>
<!--自定义事件监听器-->
    <bean class="common.event.CustomEventListener"/>
<!--容器关闭事件监听器-->
    <bean class="common.event.ContextClosedEventListener"/>
</beans>

ContextClosedEventListener

/**
 * ● @author: YiHui
 * ● @date: Created in   2023/9/10
 * ● @notes: 容器关闭事件监听器
 */
public class ContextClosedEventListener implements ApplicationListener<ContextClosedEvent> {
	@Override
	public void onApplicationEvent(ContextClosedEvent event) {
		System.out.println(this.getClass().getName()+ "::我听到了-容器关闭了-开始执行ContextClosedEvent");
	}
}

ContextRefreshedEventListener

/**
 * ● @author: YiHui
 * ● @date: Created in   2023/9/10
 * ● @notes: 容器刷新事件监听器
 */
public class ContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> {
	@Override
	public void onApplicationEvent(ContextRefreshedEvent event) {
		System.out.println(this.getClass().getName()+ "::我听到了-容器刷新了-开始执行ContextRefreshedEvent");
	}
}

 自定义事件和监听器

/**
 * ● @author: YiHui
 * ● @date: Created in   2023/9/10
 * ● @notes:自定义事件
 */
public class CustomEvent extends ApplicationContextEvent {
	public CustomEvent(ApplicationContext source) {
		super(source);
	}
}
public class CustomEventListener implements ApplicationListener<CustomEvent> {

	@Override
	public void onApplicationEvent(CustomEvent event) {

		System.out.println(this.getClass().getName()+ "::我听到了-自定义监听器-开始执行我的事件");
	}
}

单元测试

public class EventAndEventListenerTest {
    @Test
    public void testEventListener() throws Exception {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:event-and-event-listener.xml");
        applicationContext.publishEvent(new CustomEvent(applicationContext));

        applicationContext.registerShutdownHook();//或者applicationContext.close()主动关闭容器;
    }
}

测试结果

common.event.ContextRefreshedEventListener::我听到了-容器刷新了-开始执行ContextRefreshedEvent
common.event.CustomEventListener::我听到了-自定义监听器-开始执行我的事件
common.event.ContextClosedEventListener::我听到了-容器关闭了-开始执行ContextClosedEvent


        

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

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

相关文章

Hadoop的HDFS的集群安装部署

注意&#xff1a;主机名不要有/_等特殊的字符&#xff0c;不然后面会出问题。有问题可以看看第5点&#xff08;问题&#xff09;。 1、下载 1.1、去官网&#xff0c;点下载 下载地址&#xff1a;https://hadoop.apache.org/ 1.2、选择下载的版本 1.2.1、最新版 1.2.2、其…

SQL数据库查询超时,查询数据库的哪些表被上锁的语句

1.异常提示 2.表语句 2.1 查询锁表的语句 select request_session_id spid,OBJECT_NAME(resource_associated_entity_id) tableName from sys.dm_tran_locks where resource_typeOBJECT * 若是下面没有显示内容&#xff0c;说明当前没有锁住的表 2.2若是有显示锁住的表&#…

【实践篇】Redis最强Java客户端(一)之Redisson入门介绍

Redisson入门介绍 文章目录 Redisson入门介绍1.1 Redisson简介1.1.1 起源和历史1.1.2 优势和特点1.1.3 与其他Java Redis客户端的比较 1.2 使用和配置1.2.1 依赖和SDK1.2.2 配置文件解析1.2.3 连接池配置 1.3 优雅的让Hash的某个Field过期2. 参考资料3. 源码地址4. Redis从入门…

9. xaml ComboBox控件

1.运行图像 2.运行源码 a.Xaml源码 <Grid Name="Grid1"><!--IsDropDownOpen="True" 默认就是打开的--><ComboBox x:Name="co

flink的网络缓冲区

背景 在flink的taskmanager进行数据交互的过程中&#xff0c;网络缓冲区是一个可以提升网络交换速度的设计&#xff0c;此外&#xff0c;flink还通过网络缓冲区实现其基于信用值credit的流量控制&#xff0c;以便尽可能的处理数据倾斜问题 网络缓冲区 在flink中每个taskmana…

Jetsonnano B01 笔记6:开启USB摄像头

今日继续我的Jetsonnano学习之路&#xff0c;今日尝试开启一下USB摄像头&#xff0c;显示拍摄的内容。 测试代码是搬运的官方说明&#xff0c;这里只是作笔记记录学习&#xff1a; 目录 额外模块准备&#xff1a; 测试代码分析&#xff1a; 运行效果&#xff1a; 额外模块准…

【Windows】磁盘管理无法删除卷

磁盘管理无法删除卷 由于HP Cloud Recovery Tool在对U盘分区时出现闪退 尝试在Windows磁盘管理中使U盘恢复“未分配状态” 右键删除卷出现报错 虚拟磁盘管理器&#xff1a;不支持该请求 ✨解决方案 使用diskpart命令行工具 在Terminal运行如下命令行 diskpart # 列出所有…

C++ day 3

1、 自行封装一个栈的类&#xff0c;包含私有成员属性&#xff1a;栈的数组、记录栈顶的变量&#xff0c;成员函数完成&#xff1a;构造函数、析构函数、拷贝构造函数、入栈、出栈、清空栈、判空、判满、获取栈顶元素、求栈的大小 stack.h #ifndef STACK_H #define STACK_H#…

业务安全详解

文章目录 一、 业务安全概述1.1 业务安全现状1.1.1 业务逻辑漏洞1.1.2 黑客攻击的目标 二、 业务安全测试2.1 业务安全测试流程2.1.1 测试准备2.1.2 业务调研2.1.3 业务建模2.1.4 业务流程梳理2.1.5 业务风险点识别2.1.6 开展测试2.1.7 撰写报告 三、 业务安全经典场景3.1 业务…

【Java 基础篇】Java Set 集合详解:轻松管理不重复元素

在 Java 编程中&#xff0c;集合是一个非常重要的概念&#xff0c;它允许我们有效地存储和管理一组对象。其中之一是 Set 集合&#xff0c;它是一种无序、不重复的数据结构&#xff0c;非常适合用于存储不重复的元素。本篇博客将深入探讨 Java 中的 Set 集合&#xff0c;从基本…

数电课程设计——课设二:交通信号灯

一、实验内容 &#xff08;1&#xff09;十字路口有 x、y 方向两组交通信号灯&#xff0c;每组有红、黄、绿灯各一个&#xff1b; &#xff08;2&#xff09;设计一个交通灯控制电路&#xff0c;模拟十字路口交通灯工作情况&#xff0c;红灯亮 35s&#xff0c;黄灯亮 5s&…

IP的基础知识

IP IP指网际互连协议&#xff0c;Internet Protocol的缩写&#xff0c;是TCP/IP体系中的网络层协议。 设计IP的目的是提高网络的可扩展性&#xff1a;一是解决互联网问题&#xff0c;实现网络的互联互通&#xff1b;二是解除顶层网络应用和底层网络技术之间的耦合。 根据端到端…

btree学习笔记

简介 btree&#xff1a;balance tree&#xff0c;平衡多叉树&#xff0c;类比avl&#xff1a;平衡二叉树&#xff0c;都是有平衡的属性 (多个子树高度一致)&#xff0c;只不过是二叉和多叉的区别。 使用场景 文件系统如extfs、jffs&#xff0c;sql&#xff0c;磁盘上的索引查…

VirtualBox(内有Centos 7 示例安装)

1常见概念以及软件安装 1.1 虚拟化技术&#xff1a; 虚拟化技术指的是将计算机的各种硬件资源加以抽象、转换、分割&#xff0c;最后组合 起来的技术。其目的和作用主要是打破硬件资源不可分的情况&#xff0c;方便程序员自 己集成所需资源。 1.2 Virtual Box 其是虚拟化技术作…

[源码系列:手写spring] IOC第十三节:Bean作用域,增加prototype的支持

为了帮助大家更深入的理解bean的作用域&#xff0c;特意将BeanDefinition的双例支持留到本章节中&#xff0c;创建Bean,相关Reader读取等逻辑都有所改动。 内容介绍 在Spring中&#xff0c;Bean的作用域&#xff08;Scope&#xff09;定义了Bean的生命周期和可见性。包括单例和…

【redis进阶】基础知识简要回顾

1. 常见功能介绍 聚合统计 使用list集合的差集、并集来统计 排序统计 SortedSet&#xff08;ZSet&#xff09;统计&#xff0c;再利用分页列出权重高的元素 二值状态统计 BitMap存储&#xff0c;获取并统计 SETBIT uid:sign:3000:202008 2 1 GETBIT uid:sign:3000:202008 2…

Linux动态链接懒加载

Linux动态链接懒加载 懒加载 最近一个名词——懒加载&#xff0c;是一种与动态链接相关的技术&#xff0c;我对它有点感兴趣&#xff0c;于是决定深入了解一番。 懒加载是一种延迟加载资源的策略&#xff0c;它将资源的加载推迟到在首次访问或需要时才执行。这意味着在应用程…

Paper: 利用RNN来提取恶意软件家族的API调用模式

论文 摘要 恶意软件家族分类是预测恶意软件特征的好方法&#xff0c;因为属于同一家族的恶意软件往往有相似的行为特征恶意软件检测或分类方法分静态分析和动态分析两种&#xff1a; 静态分析基于恶意软件中包含的特定签名进行分析&#xff0c;优点是分析的范围覆盖了整个代码…

【Unity3D】UI Toolkit自定义元素

1 前言 UI Toolkit 支持通过继承 VisualElement 实现自定义元素&#xff0c;便于通过脚本控制元素。另外&#xff0c;UI Toolkit 也支持将一个容器及其所有子元素作为一个模板&#xff0c;便于通过脚本复制模板。 如果读者对 UI Toolkit 不是太了解&#xff0c;可以参考以下内容…