[源码系列:手写spring] IOC第十四节:容器事件和事件监听器
代码分支
内容介绍
事件监听器机制
Spring的容器事件和事件监听器机制允许应用程序在容器中发生特定事件时执行自定义逻辑。这是一种基于观察者模式的设计,其中容器充当主题,而事件监听器则充当观察者。
事件(Event):事件是表示应用程序中某个特定事件的对象,通常扩展自
ApplicationEvent类。您可以创建自定义事件来表示您关心的特定事件。事件监听器(Event Listener):事件监听器是一个实现了
ApplicationListener接口的Bean,它负责处理特定事件。监听器可以注册到容器中以接收特定类型的事件,并在事件发生时执行相应的操作。ApplicationEventMulticaster:这是Spring事件传播机制的关键部分,它负责将事件传播给注册的监听器。Spring提供了不同的
ApplicationEventMulticaster实现,最常见的是SimpleApplicationEventMulticaster和AsyncApplicationEventMulticaster。AbstractApplicationContext:
AbstractApplicationContext是Spring上下文的抽象基类,它包括了事件发布和监听的功能。
实现原理
ApplicationEventMulticaster:
ApplicationEventMulticaster的主要职责是管理事件监听器以及在事件发生时通知它们。它通常是Spring应用程序上下文的一部分。SimpleApplicationEventMulticaster:这是最简单的ApplicationEventMulticaster实现,它将事件同步传播给所有已注册的监听器。在事件发布时,它遍历注册的监听器列表,并调用每个监听器的onApplicationEvent方法。AsyncApplicationEventMulticaster:与SimpleApplicationEventMulticaster不同,AsyncApplicationEventMulticaster可以异步地传播事件。它使用线程池来处理事件监听器的通知,因此不会阻塞主线程。
AbstractApplicationContext:
AbstractApplicationContext是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
[源码系列:手写spring] IOC第十四节:容器事件和事件监听器的更多相关文章
- Spring源码分析 手写简单IOC容器
Spring的两大特性就是IOC和AOP. IOC Container,控制反转容器,通过读取配置文件或注解,将对象封装成Bean存入IOC容器待用,程序需要时再从容器中取,实现控制权由程序员向程序的 ...
- 《四 spring源码》手写springioc框架
手写SpringIOCXML版本 /** * 手写Spring专题 XML方式注入bean * * * */ public class ClassPathXmlApplicationContext { ...
- 从零开始手写 spring ioc 框架,深入学习 spring 源码
IoC Ioc 是一款 spring ioc 核心功能简化实现版本,便于学习和理解原理. 创作目的 使用 spring 很长时间,对于 spring 使用非常频繁,实际上对于源码一直没有静下心来学习过 ...
- 框架源码系列六:Spring源码学习之Spring IOC源码学习
Spring 源码学习过程: 一.搞明白IOC能做什么,是怎么做的 1. 搞明白IOC能做什么? IOC是用为用户创建.管理实例对象的.用户需要实例对象时只需要向IOC容器获取就行了,不用自己去创建 ...
- 《四 spring源码》手写springmvc
手写SpringMVC思路 1.web.xml加载 为了读取web.xml中的配置,我们用到ServletConfig这个类,它代表当前Servlet在web.xml中的配置信息.通过web.xml ...
- Spring源码 20 手写模拟源码
参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...
- 利用递归,反射,注解等,手写Spring Ioc和Di 底层(分分钟喷倒面试官)了解一下
再我们现在项目中Spring框架是目前各大公司必不可少的技术,而大家都知道去怎么使用Spring ,但是有很多人都不知道SpringIoc底层是如何工作的,而一个开发人员知道他的源码,底层工作原理,对 ...
- Spring源码剖析3:Spring IOC容器的加载过程
本文转自五月的仓颉 https://www.cnblogs.com/xrq730 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https ...
- 源码分析 | 手写mybait-spring核心功能(干货好文一次学会工厂bean、类代理、bean注册的使用)
作者:小傅哥 博客:https://bugstack.cn - 汇总系列原创专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言介绍 一个知识点的学习过程基本分为:运行helloworld ...
- Spring源码剖析2:Spring IOC容器的加载过程
spring ioc 容器的加载流程 1.目标:熟练使用spring,并分析其源码,了解其中的思想.这篇主要介绍spring ioc 容器的加载 2.前提条件:会使用debug 3.源码分析方法:In ...
随机推荐
- .NET 响应式编程 System.Reactive 系列文章(一):基础概念
.NET 响应式编程 System.Reactive 系列文章(一):基础概念 引言 在现代软件开发中,处理异步事件和数据流已经成为常见的需求,比如用户输入.网络请求.传感器数据等.这些数据流通常是无 ...
- 人类讨厌AI的缺点,其实自己也有,是时候反思了。
马特·科拉默摄于Unsplash 前言:人类讨厌AI,其实就是讨厌自己! 如果你问一些人对人工智能的看法,你可能会听到诸如不道德.偏见.不准确甚至操纵这样的词语. 人工智能因为种种原因正备受批评.它让 ...
- linux 手动释放内存
在 Linux 系统中,内存管理通常由系统自动处理,但在某些情况下,手动释放内存可能是必要的.例如,当业务应用比较繁忙时会频繁存取文件,物理内存会被缓存大量占用,有时会出现内存不足的情况发生,甚至会导 ...
- CDS标准视图:催款冻结描述 I_DunningBlockingReasonText
视图名称:催款冻结描述 I_DunningBlockingReasonText 视图类型:基础视图 视图代码: 点击查看代码 @EndUserText.label: 'Dunning Blocking ...
- Python · MuJoCo | MuJoCo 与 mujoco_py 的版本对应,以及安装 Cython<3
0 系统环境 & 需求 环境:Linux Ubuntu 20.04,Cuda 12.2(?)也可能是 11,我不是很清楚驱动版本- 需求:安装 gym.dm_control.MuJoCo 和 ...
- Android性能测试(内存、cpu、fps、流量、GPU、电量)——adb篇
adb 常用命令 获取连接设备号:adb devices 列出设备所有已安装的包名 (不需root权限) adb shell "pm list packages",可以加上 ...
- biancheng-Java设计模式:23种设计模式全面解析(超级详细)
http://c.biancheng.net/design_pattern/ 24种设计模式 1.创建型模式 抽象工厂模式(Abstract factory pattern): 提供一个接口, 用于创 ...
- 项目PMP之二项目运行环境
一.项目运行环境因素 项目内部:组织过程资产(OPA):用于治理和执行项目,可为正式与非正式 过程.政策和程序:由非项目内职能部门制定的,如PMO 组织知识库:项目进行中累计的信息文档,如经验.设计. ...
- 所生成项目的处理器架构“MSIL”与 “x86”不匹配
在 .net 生成时如果修改过某个类库的平台目标,那么通常会出现下面的警告: 之所以产生这个问题是因为类库的"平台目标"不统一,如果选择了 x86, 那么解决方案中所有的项目都应设 ...
- 2024年度Graph+AI开源探索思考
前记 这篇年度总结其实酝酿了许久,却因诸多原因拖至腊月底,此时赶在春节前发出来,也不失为"农历版"年度总结了.所谓年度总结,一般是"温故而知新",我不太想落入堆 ...
https://github.com/yihuiaa/little-spring/tree/event-and-event-listener