[源码系列:手写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 ...
随机推荐
- 今天记录一下小程序使用微信客服api,而不是小程序客服
小程序客服缺少很多东西,并且只能使用button的开放能力,所以尝试使用一下微信客服,自己开发客服又比较麻烦,秉着能免费绝不花钱的想法,接下来就直接写代码,也就是api,记录下来方便使用 wx.ope ...
- 史上最全的Cursor IDE教程
Cursor IDE 使用教程 1. 快速上手 1.1 入门流程 graph TD A[安装Cursor] --> B[首次启动] B --> C[选择主题和配置] C --> D[ ...
- js操作shadow-root内的DOM元素
其实就是 documentfragment元素,就是动态生成的文档碎片元素. 1,项目中在DOM结构里遇到了shadow-root(open),用JS方法无法直接获取其内的DOM元素 2.shadow ...
- 微服务实战系列(六)-网关springcloud zuul-copy
1. 场景描述 今天接着介绍springcloud,今天介绍下springcloud的路由网关-Zuul,外围系统或者用户通过网关访问服务,网关通过注册中心找到对应提供服务的客户端,网关也需要到注册中 ...
- NoSQL和SQL的区别、使用场景与选型比较
什么是NoSQL NoSQL,指的是非关系型的数据库.NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称,它具有非关系型.分布式.不提供ACID的数 ...
- k8s~控制deamonset中pod的数量
DaemonSet 是 Kubernetes 中的一种控制器,用于确保集群中的每个节点(或特定标签选择器匹配的节点)运行一个 Pod 的副本.DaemonSet 通常用于运行集群守护进程,如日志收集. ...
- Groove Intermediate pg walkthrough
80端口web站点 dirsearch 没发现啥有用信息 感觉就是让我们突破登录框进后台的 https://github.com/ChurchCRM/CRM/issues/137 上网查到默认密码 登 ...
- vim系列-文本操作篇
基数行与偶数行分组 使用Vim的替换命令,可以轻松地将基数行和偶数行分组: %s/\(^.*$\)\n\(^.*$\)/\1 \2/g 然后,删除所有的基数行: %s/^.*$\n\(^.*$\)/\ ...
- dart中Set类型详解
01==> Set 它的主要功能是去除重复的数组内容: Set是没有顺序且不能够重复的数组,所以不能够通过索引值去获取内容 var s = new Set(); s.add('苹果'); s.a ...
- C#/.NET/.NET Core优秀项目和框架2025年1月简报
前言 公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的详细介绍.功能特点.使用方式以及部分功能 ...
https://github.com/yihuiaa/little-spring/tree/event-and-event-listener