Spring事件监听机制

news2024/11/17 3:23:19

前言

事件监听机制其原理就是观察者模式,而观察者模式又被称为发布-订阅模式

观察者模式将有依赖关系的对象抽象为了观察者和主题两个不同的角色,多个观察者同时观察一个主题,两者只通过抽象接口保持松耦合状态,这样双方可以相对独立的进行扩展和变化:比如可以很方便的增删观察者,修改观察者中的更新逻辑而不用修改主题中的代码。但是这种解耦进行的并不彻底,这具体体现在以下几个方面:
• ① 抽象主题需要依赖抽象观察者,而这种依赖关系完全可以去除。
• ② 主题需要维护观察者列表,并对外提供动态增删观察者的接口。
• ③ 主题状态改变时需要由自己去通知观察者进行更新。

耦合这个词在平常的开发工作中应该不陌生,简单理解就是代码中各部分关联度过高。举一个大家都遇见过的经典耦合场景:用户注册成功之后需要进行发送短信通知或是邮件通知,用户注册逻辑与发送短信或是邮件通知逻辑放在一块就是一种耦合现象,如果短信或是邮件功能异常,整个用户注册功能就会异常,会带来不好的用户体验,另外的缺点是维护复杂,不便于拓展。将业务逻辑与功能性逻辑进行拆分开就是属于解耦。spring中IOC思想就是解耦的一种体现,毕竟在一个实现类中如果调用另一个实现类不用繁琐的创建对象,只需要把需要用到实现类进行注入就可以了,减少代码量的同时也便于后期的拓展与维护。

可以把主题(Subject)替换成事件(event),把对特定主题进行观察的观察者(Observer)替换成对特定事件进行监听的监听器(EventListener),而把原有主题中负责维护主题与观察者映射关系以及在自身状态改变时通知观察者的职责从中抽出,放入一个新的角色事件发布器(EventPublisher)中,事件监听模式的轮廓就展现在了我们眼前,如下图所示:
在这里插入图片描述
常见事件监听机制的主要角色如下:
事件及事件源:对应于观察者模式中的主题。事件源发生某事件是特定事件监听器被触发的原因。
事件监听器:对应于观察者模式中的观察者。监听器监听特定事件,并在内部定义了事件发生后的响应逻辑。
事件发布器:事件监听器的容器,对外提供发布事件和增删事件监听器的接口,维护事件和事件监听器

事件监听机制的应用场景

在程序设计里有哪些场景会用到呢?下面举一些例子: 用户注册或者登陆,或者用户下单成功都需要发个短信或者邮件提示用户,简单抽象一下,用户的很多业务行为都会有要发短信的需求,如果每个业务行为都各自发短信,那么首先业务行为与发短信就耦合了,不利于后续扩展,其次具有相同特征的行为一直重复。使用事件监听机制改造一下,把各个业务行为内的短信内容封装成一个事件,业务行为触发的时候发布这个事件,再把具体发短信的行为封装到监听器里,监听到事件源发布的事件后,触发具体的发短信的行为。

事件监听机制的好处

业务与业务之间解耦,符合依赖倒置原则;
代码复用,更容易维护,也提高了执行效率;

Spring容器对事件监听机制的支持

Spring容器,具体而言是ApplicationContext接口定义的容器提供了一套相对完善的事件发布和监听框架,其遵循了JDK中的事件监听标准,并使用容器来管理相关组件,使得用户不用关心事件发布和监听的具体细节,降低了开发难度也简化了开发流程。下面看看对于事件监听机制中的各主要角色,Spring框架中是如何定义的,以及相关的类体系结构。

事件

Spring为容器内事件定义了一个抽象类ApplicationEvent,该类继承了JDK中的事件基类EventObject。因而自定义容器内事件除了需要继承ApplicationEvent之外,还要传入事件源作为构造参数。

在这里插入图片描述

事件监听器

Spring定义了一个ApplicationListener接口作为事件监听器的抽象,接口定义如下:

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/**
	 * Handle an application event.
	 * @param event the event to respond to
	 */
	void onApplicationEvent(E event);

}

(1)该接口继承了JDK中表示事件监听器的标记接口EventListener,内部只定义了一个抽象方法onApplicationEvent(evnt),当监听的事件在容器中被发布,该方法将被调用。
(2)同时,该接口是一个泛型接口,其实现类可以通过传入泛型参数指定该事件监听器要对哪些事件进行监听。这样有什么好处?这样所有的事件监听器就可以由一个事件发布器进行管理,并对所有事件进行统一发布,而具体的事件和事件监听器之间的映射关系,则可以通过反射读取泛型参数类型的方式进行匹配,稍后我们会对原理进行讲解。
(3)最后,所有的事件监听器都必须向容器注册,容器能够对其进行识别并委托容器内真正的事件发布器进行管理。

事件发布器

ApplicationContext接口继承了ApplicationEventPublisher接口,从而提供了对外发布事件的能力,如下所示:
在这里插入图片描述
那么是否可以说ApplicationContext,即容器本身就担当了事件发布器的角色呢?其实这是不准确的,容器本身仅仅是对外提供了事件发布的接口,真正的工作其实是委托给了具体容器内部一个ApplicationEventMulticaster对象,其定义在AbstractApplicationContext抽象类内部,如下所示:
在这里插入图片描述
在这里插入图片描述

所以,真正的事件发布器是ApplicationEventMulticaster,这是一个接口,定义了事件发布器需要具备的基本功能:管理事件监听器以及发布事件。
在这里插入图片描述
其默认实现类是
SimpleApplicationEventMulticaster,该组件会在容器启动时被自动创建,并以单例的形式存在,管理了所有的事件监听器,并提供针对所有容器内事件的发布功能。

在这里插入图片描述

AbstractApplicationContext#refresh方法中的initApplicationEventMulticaster()步骤会判断是否存在,不存在就实例化一个SimpleApplicationEventMulticaster。

具体实现细节看
Spring事件监听源码解析

基于Spring实现对任务执行结果的监听

基于Spring框架来实现对自定义事件的监听需要三步:
• ① 继承spring事件基类ApplicationEvent,完成事件的封装;
• ② 实现ApplicationListener接口或者通过@EventListener注解,把事件监听器注册到spring容器里;
• ③ 业务类要实现ApplicationEventPublisherAware接口或者引用ApplicationContext,把spring的事件发布器注入到业务类里,在触发事件的时候,spring的事件发布器发布事件;

自定任务结束事件

定义一个任务结束事件SignInEvent,该类继承抽象类ApplicationEvent来遵循容器事件规范。

public class SignInEvent extends ApplicationEvent {
    private static final long serialVersionUID = -578995207794413821L;

    /**
     * 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 SignInEvent(Object source) {
        super(source);
    }
}

自定义短信服务监听器并向容器注册

该类实现了容器事件规范定义的监听器接口,通过泛型参数指定对上面定义的任务结束事件进行监听,通过@Component注解向容器进行注册。

@Slf4j
@Component
public class MesTaskListener1 implements ApplicationListener<SignInEvent> {
    @Override
    public void onApplicationEvent(SignInEvent event) {
        User user = (User) event.getSource();

        // 调用具体的发送短信接口
        log.info("send message to user : {}", JSON.toJSONString(user));
    }
}

也可以通过注解的方式实现

@Slf4j
@Component
public class MesTaskListener2 {

    @EventListener
    public void doSendMesEvent(SignInEvent event) {
        User user = (User) event.getSource();

        // 调用具体的发送短信接口
        log.info("send message to user : {}", JSON.toJSONString(user));
    }
}

发布事件

从上面对Spring事件监听机制的类结构分析可知,发布事件的功能定义在ApplicationEventPublisher接口中,而ApplicationContext继承了该接口,所以最好的方法是通过实现ApplicationContextAware接口获取ApplicationContext实例,然后调用其发布事件方法。如下所示定义了一个发布容器事件的代理类:

@Component
public class SignInServiceImpl implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    public Boolean signIn(User user) {
        // 登录成功
        // 发布事件
        publishEvent(new SignInEvent(user));

        return Boolean.TRUE;
    }

    // 发布事件
    public void publishEvent(ApplicationEvent event) {
        applicationContext.publishEvent(event);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

因为ApplicationContext继承了ApplicationEventPublisher接口,也可以直接通过ApplicationContext调用其发布事件方法。

@Component
public class SignInServiceImpl2 {
    @Autowired
    private ApplicationContext applicationContext;

    public Boolean signIn(User user) {
        // 登录成功
        // 发布事件
        applicationContext.publishEvent(new SignInEvent(user));

        return Boolean.TRUE;
    }
}

在此基础上,还可以自定义一个邮件服务监听器,在任务执行结束时发送邮件通知用户。过程和上面自定义短信服务监听器类似:实现ApplicationListner接口并重写抽象方法,然后通过注解或者xml的方式向容器注册。

@Slf4j
@Component
public class EmailTaskListener {

    @EventListener
    public void doSendEmailEvent(SignInEvent event) {
        User user = (User) event.getSource();

        // 调用具体的发送短信接口
        log.info("send Email to user : {}", JSON.toJSONString(user));
    }
}

测试发布事件

@RunWith(SpringRunner.class)
@SpringBootTest()
public class SpringTest {
    @Autowired
    private SignInServiceImpl1 signInService;

    @Test
    public void testEvent() {
        signInService.signIn(User.builder().id(123546L).name("小明").phone("15232654678").build());
    }
}

测试结果

在这里插入图片描述

看到这里是不是有个疑问?

1.如果想要改变自定义监听器的执行顺序该怎么做?
2.如果其中某个自定义事件发生异常,会不会影响其它自定义事件?

我们来看下:
1.执行顺序
注解方式:
使用注解 @Order()@EventListener
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意:Order的值越小,优先级越高

2.异常问题
我们自己写一个异常

在这里插入图片描述
在这里插入图片描述

可以看到一个事件发生异常后,剩余的事件都不能执行,这个时候我们可以使用@Async 注解来解决

在这里插入图片描述
在这里插入图片描述
启动后发现:
在这里插入图片描述
task-scheduler-1发送短信的异常并不影响task-scheduler-2发送邮箱业务

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

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

相关文章

C++11并发与多线程笔记(10) future其他成员函数、shared_future、atomic

C11并发与多线程笔记&#xff08;10&#xff09; future其他成员函数、shared_future、atomic 1、std::future 的成员函数1.1 std::future_status 2、std::shared_future&#xff1a;也是个类模板3、std::atomic原子操作3.1 原子操作概念引出范例&#xff1a;3.2 基本的std::at…

CTFhub-sql注入-绕过空格过滤

常用绕过空格过滤的方法&#xff1a; /**/、()、%0a 1.判断是否存在sqli注入 1 1/**/union/**/select/**/11 1/**/union/**/select/**/12 如果1/**/union/**/select/**/11的显示结果与1/**/union/**/select/**/12的显示结果不一样&#xff0c; 与1的结果一样说明存在注入…

JMeter中利用Jython运行Python代码

介绍 Jython是Python和Java的结合。Jython语法和Python一样&#xff0c;不但可以使用Python的库&#xff0c;而且还可以调用Java的库。结合了Python和Java的优点&#xff0c;也就是说Jython既有动态语言的灵活性&#xff0c;又可以用静态语言的强大的类库。其实&#xff0c;我…

Unity进阶–通过PhotonServer实现联网登录注册功能(客户端)–PhotonServer(三)

文章目录 Unity进阶–通过PhotonServer实现联网登录注册功能(客户端)–PhotonServer(三)前情提要客户端部分 Unity进阶–通过PhotonServer实现联网登录注册功能(客户端)–PhotonServer(三) 前情提要 单例泛型类 using System.Collections; using System.Collections.Generic; …

Android6:片段和导航

创建项目Secret Message strings.xml <resources><string name"app_name">Secret Message</string><string name"welcome_text">Welcome to the Secret Message app!Use this app to encrypt a secret message.Click on the Star…

周末时间在家重新做了一个电脑系统,手艺没有丢!!!

有个朋友的电脑抱怨自己太卡&#xff0c;有缘见过几次他的电脑&#xff0c;确实哦&#xff0c;10年的老笔记本了&#xff0c;关键还是日本买的东芝t552,配置4G500G&#xff0c;昨天晚上朋友提过来的时候&#xff0c;大吃已经&#xff0c;还以为是电磁炉呢。看下面的图片就知道了…

平方数之和(力扣)双指针 JAVA

给定一个非负整数 c &#xff0c;你要判断是否存在两个整数 a 和 b&#xff0c;使得 a&#xff3e;2 b&#xff3e;2 c 。 示例 1&#xff1a; 输入&#xff1a;c 5 输出&#xff1a;true 解释&#xff1a;1 * 1 2 * 2 5 示例 2&#xff1a; 输入&#xff1a;c 3 输出&am…

Linux系统调试——核心转储(core dump)

本篇讲解Linux应用程序发生Segmentation fault段错误时&#xff0c;如何利用core dump文件定位错误。 核心转储 在 Linux 系统中&#xff0c;常将“主内存”称为核心(core)&#xff0c;而核心映像(core image) 就是 “进程”(process)执行当时的内存内容。 当进程发生错误或…

计算机竞赛 图像识别-人脸识别与疲劳检测 - python opencv

文章目录 0 前言1 课题背景2 Dlib人脸识别2.1 简介2.2 Dlib优点2.3 相关代码2.4 人脸数据库2.5 人脸录入加识别效果 3 疲劳检测算法3.1 眼睛检测算法3.3 点头检测算法 4 PyQt54.1 简介4.2相关界面代码 5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是…

LVS-DR模型实例

一、LVS-DR集群介绍 LVS-DR&#xff08;Linux Virtual Server Director Server&#xff09;工作模式&#xff0c;是生产环境中最常用的一 种工作模式。 1、LVS-DR 工作原理 LVS-DR 模式&#xff0c;Director Server 作为群集的访问入口&#xff0c;不作为网关使用&#xff0…

借助Midjourney创作龙九子图

&#xff08;本文阅读时间&#xff1a;5 分钟&#xff09; 《西游记》中有这么一段描写&#xff1a; 龙王道&#xff1a;“舍妹有九个儿子。那八个都是好的。第一个小黄龙&#xff0c;见居淮渎&#xff1b;第二个小骊龙&#xff0c;见住济渎&#xff1b;第三个青背龙&#xff0…

Linux 消息队列的创建与使用

消息队列的创建与使用 进程a发送一条消息&#xff0c;进程b读取消息。 a.c代码&#xff1a; b.c代码&#xff1a; 1.a进程创建向消息队列&#xff0c;并向消息队列中发送消息 运行a程序之前&#xff0c;当前系统中消息队列的数量为0&#xff1a; 运行一次a程序&#xff0c;消…

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存

JVM——引言JVM内存结构_北岭山脚鼠鼠的博客-CSDN博客 书接上回内存结构——方法区。 这里常量池是运行时常量池。 方法区 面试题 intern()方法 intern() 方法用于在运行时将字符串添加到内部的字符串池stringtable中&#xff0c;并返回字符串池stringtable中的引用。 返…

【rust/egui】(三)看看template的app.rs:序列化、持久化存储

说在前面 rust新手&#xff0c;egui没啥找到啥教程&#xff0c;这里自己记录下学习过程环境&#xff1a;windows11 22H2rust版本&#xff1a;rustc 1.71.1egui版本&#xff1a;0.22.0eframe版本&#xff1a;0.22.0上一篇&#xff1a;这里 serde app.rs中首先定义了我们的Templ…

2023.8.19-2023.8.XX 周报【人脸3D+虚拟服装方向基础调研-Cycle Diffusion\Diffusion-GAN\】更新中

学习目标 1. 这篇是做diffusion和gan结合的&#xff0c;可以参照一下看看能不能做cyclegan的形式&#xff0c;同时也可以调研一下有没有人follow这篇论文做了类似cyclegan的事情 Diffusion-GAN论文精读https://arxiv.org/abs/2206.02262 2. https://arxiv.org/abs/2212.06…

python——json、字典的区别及相互转换方法

前言 json&#xff0c;是一种轻量级的数据交换格式&#xff0c;由JavaScript语言创建&#xff0c;广泛应用于网页数据交互&#xff0c;常见于爬虫和数据分析领域。 json格式简洁、结构清晰&#xff0c;存储格式为&#xff1a;键值对&#xff08;key:value&#xff09; 在pytho…

创作的1024天 分享月入5K的副业心得

机缘 今天早上醒来打开电脑&#xff0c;和往常一样点开csdn&#xff0c;看见有一封私信&#xff0c;原来是系统通知&#xff0c;今天是我成为创作者的1024天&#xff0c;那就趁着这个机会&#xff0c;分享一下目前我月入5K的副业心得。我是一个普通人&#xff0c;最初想成为创…

【100天精通python】Day41:python网络爬虫开发_爬虫基础入门

目录 专栏导读 1网络爬虫概述 1.1 工作原理 1.2 应用场景 1.3 爬虫策略 1.4 爬虫的挑战 2 网络爬虫开发 2.1 通用的网络爬虫基本流程 2.2 网络爬虫的常用技术 2.3 网络爬虫常用的第三方库 3 简单爬虫示例 专栏导读 专栏订阅地址&#xff1a;https://blog.csdn.net/…

提高 Snowflake 工作效率的 6 大工具

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可二次编辑的3D应用场景 Snowflake 彻底改变了企业存储、处理和分析数据的方式&#xff0c;提供了无与伦比的灵活性、可扩展性和性能。但是&#xff0c;与任何强大的技术一样&#xff0c;要真正利用其潜力&#xff0c;必须拥有…

vsCode使用cuda

一、vsCode使用cuda 前情提要&#xff1a;配置好mingw&#xff1a; 1.安装cuda 参考&#xff1a; **CUDA Toolkit安装教程&#xff08;Windows&#xff09;&#xff1a;**https://blog.csdn.net/qq_42951560/article/details/116131410 2.在vscode中添加includePath c_cp…