模拟Spring事件监听机制

news2024/11/15 20:37:29
作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

之前我们一起学习了Spring事件监听机制,已经忘了的同学可以先去回顾一下。

当时我在文章里留了一个问题:

现在,终于到了填坑的时候了!为了把这个问题讲清楚,我打算模拟一个山寨版的Spring事件监听机制。

然而,重新回顾却发现:当时太年轻了,刚学完Spring源码,巴不得秀一波骚操作,所以写得很复杂,可读性较差(你看看网上很多文章,都犯了我以前的毛病)。既然这篇文章是为了让大家明白底层原理,那么“让大家懂”比什么都重要。

所以这次重写山寨版Spring事件监听机制,我去除了很多非核心类,把所有逻辑都压缩到ApplicationContext中,希望能带来更好的阅读体验。

事件监听机制?还是观察者模式?

很多人总是执着于外在的表象,并在“它究竟是什么”这个问题上争论不休,却很少去了解它能做什么。在对待设计模式这个问题上,很多人执着于“它究竟是哪种模式”,却不去理会“这种模式能解决什么问题”。就像Spring的事件监听机制,它其实是什么模式都不重要,重要的是了解它可以解决什么问题。

说起来也是GOF害的,当年编写了一本《Design Patterns: Elements of Reusable Object-Oriented Software》(中文译名《设计模式》),举例了常见的23种设计模式,结果内容实在抽象,很多人看完云里雾里,只记得了“23”这个数字,然后以为天底下只有23种设计模式,每每见到一种模式,就想着往上面套...发现那里有点不一致,就浑身难受,到处问人:哎呀,这个到底是啥模式呀...

去他的,一点都不重要!设计模式最重要的就是设计原则,而不是具体的模式,理解了设计原则才能写出好的设计模式。无招胜有招,才是一种境界。

设计思路

代码

简单的maven项目即可

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <configuration>
        <source>8</source>
        <target>8</target>
      </configuration>
    </plugin>
  </plugins>
</build>

<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
  </dependency>
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.16</version>
  </dependency>
</dependencies>

自定义事件

/**
 * 退单事件
 */
public class OrderCanceledEvent extends Event {
    public OrderCanceledEvent(Object source) {
        super(source);
    }
}

/**
 * 下单事件
 */
public class OrderCompletedEvent extends Event {
    public OrderCompletedEvent(Long source) {
        super(source);
    }
}

业务代码

/**
 * 短信服务,监听下单事件,下单后发短信通知用户
 */
public class SmsService implements Listener<OrderCompletedEvent> {
    @SneakyThrows
    @Override
    public void onApplicationEvent(OrderCompletedEvent event) {
        System.out.println("下单成功!您的订单号是: " + event.getSource());
        TimeUnit.SECONDS.sleep(2);
    }

    @Override
    public boolean supportsEventType(Event event) {
        return event instanceof OrderCompletedEvent;
    }

}

/**
 * 物流服务,监听下单事件,用户下单后发货
 */
public class CarService implements Listener<OrderCompletedEvent> {
    @SneakyThrows
    @Override
    public void onApplicationEvent(OrderCompletedEvent event) {
        System.out.println("订单" + event.getSource() + "已经发货!");
        TimeUnit.SECONDS.sleep(2);
    }

    @Override
    public boolean supportsEventType(Event event) {
        return event instanceof OrderCompletedEvent;
    }
}

/**
 * 退款服务,监听取消订单事件,为用户退款
 */
public class RefundService implements Listener<OrderCanceledEvent> {
    @SneakyThrows
    @Override
    public void onApplicationEvent(OrderCanceledEvent event) {
        System.out.println("退款成功!" + event.getSource() + "元已经退回原账户");
        TimeUnit.SECONDS.sleep(2);
    }

    @Override
    public boolean supportsEventType(Event event) {
        return event instanceof OrderCanceledEvent;
    }
}

核心实现

ApplicationContext

public class ApplicationContext {

    private final Set<Listener<?>> listeners = new LinkedHashSet<>();

    /**
     * 注册bean(监听器)
     *
     * @param listener
     */
    public void registerListener(Listener<?> listener) {
        listeners.add(listener);
    }

    /**
     * 发布事件
     *
     * @param event
     */
    public void publish(Event event) {
        Set<Listener<?>> matchedListeners = getMatchedListeners(event);
        matchedListeners.forEach(listener -> this.invokeListener(listener, event));
    }

    // ------------- private methods -------------
    private Set<Listener<?>> getMatchedListeners(Event event) {
        if (listeners.isEmpty()) {
            return Collections.emptySet();
        }

        return listeners.stream()
                .filter(listener -> listener.supportsEventType(event))
                .collect(Collectors.toSet());
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private void invokeListener(Listener listener, Event event) {
        listener.onApplicationEvent(event);
    }

}

event

/**
 * 事件
 */
@Getter
public class Event {

    private Object source;

    public Event(Object source) {
        this.source = source;
    }
}

Listener

/**
 * 监听器
 *
 * @param <E>
 */
public interface Listener<E extends Event> {

    /**
     * 事件发生时触发
     *
     * @param event
     */
    void onApplicationEvent(E event);

    /**
     * 监听器是否匹配
     *
     * @param event
     * @return
     */
    boolean supportsEventType(Event event);

}

测试

public class EventTest {

    private ApplicationContext applicationContext = new ApplicationContext();

    // 模拟Spring启动,初始化容器并注册bean
    @Before
    public void refreshApplication() {
        applicationContext.registerListener(new SmsService());
        applicationContext.registerListener(new CarService());
        applicationContext.registerListener(new RefundService());
    }

    // 模拟下单
    @Test
    public void orderCompletedService() {
        // 扣减库存...

        // 生成订单... orderId=10086

        // 订单流水...

        // 下单成功,发布事件
        applicationContext.publish(new OrderCompletedEvent(10086L));
    }

    // 模拟取消订单
    @Test
    public void orderCanceledService() {
        // 回退库存...

        // 更改订单状态... orderId=10086

        // 订单流水...

        // 订单取消成功,发布事件
        applicationContext.publish(new OrderCanceledEvent(10086L));
    }
}

优化:支持异步事件

之前提到过,Spring的事件监听机制模式是同步的:

上面代码有3个角色,OrderService发布事件、SmsService监听事件(sleep 2s)、CarService监听事件(sleep 2s)

那么,怎么才能支持异步呢?引入线程池即可:

public class ApplicationContext {

    private final Set<Listener<?>> listeners = new LinkedHashSet<>();

    // 支持异步事件
    @Nullable
    private Executor taskExecutor;

    /**
     * 注册bean(监听器)
     *
     * @param listener
     */
    public void registerListener(Listener<?> listener) {
        listeners.add(listener);
    }

    /**
     * 发布事件
     *
     * @param event
     */
    public void publish(Event event) {
        Set<Listener<?>> matchedListeners = getMatchedListeners(event);
        for (Listener<?> listener : matchedListeners) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                // 如果外界设置了线程池,则变为异步事件
                executor.execute(() -> invokeListener(listener, event));
            } else {
                // 默认同步事件
                invokeListener(listener, event);
            }
        }
    }

    // ------------- private methods -------------
    private Set<Listener<?>> getMatchedListeners(Event event) {
        if (listeners.isEmpty()) {
            return Collections.emptySet();
        }

        return listeners.stream()
                .filter(listener -> listener.supportsEventType(event))
                .collect(Collectors.toSet());
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private void invokeListener(Listener listener, Event event) {
        listener.onApplicationEvent(event);
    }

    @Nullable
    public Executor getTaskExecutor() {
        return this.taskExecutor;
    }

    public void setTaskExecutor(Executor executor) {
        this.taskExecutor = executor;
    }

}

至此,本篇完。初看不以为意,但你如果再扫一眼,就会发现整个工程只引入了Lombok和Junit,几乎全部依赖都来自于JDK本身。也就是说,Spring最大的用处只不过是提供了自动装配(DI),它的事件监听机制本身和DI没太大关系,JDK足矣。

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析

阶段4、深入jdk其余源码解析

阶段5、深入jvm源码解析

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

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

相关文章

polar CTF 简单rce

一、题目 <?php /*PolarD&N CTF*/ highlight_file(__FILE__); function no($txt){if(!preg_match("/cat|more|less|head|tac|tail|nl|od|vim|uniq|system|proc_open|shell_exec|popen| /i", $txt)){return $txt;}else{ die("whats up");}} $yyds(…

向日葵远程控制软件MySQL5.7的安装与配置

目录 一. 向日葵远程控制软件 1.1 简介 1.2 选择原因 1.3 安装及使用 1.4 使用场景 二. MySQL5.7 安装与配置 2.1 什么是MySQL 2.2 安装 MySQL5.7 2.2.1 安装步骤 2.2.2 内部连接 2.2.3 外部连接 三. 思维导图 一. 向日葵远程控制软件 1.1 简介 向日葵电脑版是一款拥有多年…

SpringCloudAlibaba之Gateway

1、简介 网关是系统唯一对外的入口&#xff0c;介于客户端与服务器端之间&#xff0c;用于对请求进行鉴权、限流、路由、监控等功能。 2、Gateway主要功能 2.1、route 路由 路由是网关的最基本组成&#xff0c;由一个路由 id、一个目标地址 url&#xff0c;一组断言工厂及一…

掌握静态S5:从入门到精通的指南

在现今的数据驱动时代&#xff0c;静态S5作为一款强大的数据分析工具&#xff0c;越来越受到各行各业的青睐。然而&#xff0c;如何从入门到精通&#xff0c;全面掌握静态S5的各项功能&#xff0c;成为了许多用户面临的挑战。本文将为你提供一份详尽的指南&#xff0c;助你顺利…

Lingo 17安装包下载及安装教程

Lingo 17下载链接&#xff1a;https://docs.qq.com/doc/DUndEVXd4WVVweGFR 1.鼠标右键解压到“Lingo 17.0” 2.双击打开【Setup】文件夹 3.选中Lingo 17.0&#xff0c;鼠标右键选择“以管理员身份运行” 4.点击“Next” 5.选中I accept the terms in the license agreement&…

【C程序设计】C循环

有的时候&#xff0c;我们可能需要多次执行同一块代码。一般情况下&#xff0c;语句是按顺序执行的&#xff1a;函数中的第一个语句先执行&#xff0c;接着是第二个语句&#xff0c;依此类推。 编程语言提供了更为复杂执行路径的多种控制结构。 循环语句允许我们多次执行一个…

Python 热力图的绘制(Matplotlib篇-12)

Python 热力图的绘制(Matplotlib篇-12)         🍹博主 侯小啾 感谢您的支持与信赖。☀️ 🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ🌹꧔ꦿ�…

陆面过程模式CLM、地球系统模式CESM安装及快速运行

目录 专题一 CESM、CLM运行条件及Linux编译基础 专题二 CESM、CLM基础 专题三 CLM程序获取、结构及其功能 专题四 CLM移植、安装及快速运行 专题五 CLM配置选项及数据文件制备 专题六 CLM单点或区域运行 专题七 CLM结果处理、分析及可视化 专题八 CLM代码修改、发展及改…

二、UI文件设计与运行机制

一、UI文件设计与运行机制 1、创建工程 2、添加控件&#xff0c;实现按钮点击 &#xff08;1&#xff09;添加控件 &#xff08;2&#xff09;添加信号和槽 2、分析项目结构 test_02test_02.pro Qt工程文件Headerswidget.h 设计的窗体类的头文件Sourcesmain.cpp 主程序入…

diffusers 源码待理解之处

一、训练DreamBooth时&#xff0c;相关代码的细节小计 ** class_labels timesteps 时&#xff0c;模型的前向传播怎么走&#xff1f;待深入去看 ** 利用class_prompt去生成数据&#xff0c;而不是instance_prompt class DreamBoothDataset(Dataset):"""A dat…

H5C3练习心得 2024.01.03(文字加载动画效果)--transition,动画渲染,遮罩层

&#xff08;一&#xff09;transition&#xff08;过渡效果&#xff09; 1.详解 通常将css的属性值更改后&#xff0c;浏览器会立即更新新的样式&#xff0c;例如在鼠标悬停在元素上时&#xff0c;通过 :hover 选择器定义的样式会立即应用在元素上。 在 CSS3 中加入了一项过…

Java虚拟机介绍

JVM是一种用于计算设备的规范&#xff0c;它是一个虚拟出来的计算机&#xff0c;是通过在实际的计算机上仿真模拟计算机的各个功能来实现的。Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。每个Java虚拟机都着一个清晰的任务&#x…

5分钟理解什么是多模态

大家好&#xff0c;我是董董灿。 大模型越来越多了&#xff0c;大模型下沉的行业也越来越多。前几周一个在电厂工作的老哥发消息问我&#xff1a;大模型中所谓的多模态是什么意思&#xff1f; 我当时大概跟他解释了一下。 其实在人工智能领域&#xff0c;我们经常会听到&quo…

leetcode递归算法题总结

递归本质是找重复的子问题 本章目录 1.汉诺塔2.合并两个有序链表3.反转链表4.两两交换链表中的节点5.Pow(x,n) 1.汉诺塔 汉诺塔 //面试写法 class Solution { public:void hanota(vector<int>& a, vector<int>& b, vector<int>& c) {dfs(a,b…

[DevOps-02] Code编码阶段工具

一、简要说明 在code阶段,我们需要将不同版本的代码存储到一个仓库中,常见的版本控制工具就是SVN或者Git,这里我们采用Git作为版本控制工具,GitLab作为远程仓库。 Git安装安装GitLab配置GitLab登录账户二、Git安装 Git官网 Githttps://git-scm.com/

HarmonyOS-ArkTS基本语法及声明式UI描述

初识ArkTS语言 ArkTS是HarmonyOS优选的主力应用开发语言。ArkTS围绕应用开发在TypeScript&#xff08;简称TS&#xff09;生态基础上做了进一步扩展&#xff0c;继承了TS的所有特性&#xff0c;是TS的超集。因此&#xff0c;在学习ArkTS语言之前&#xff0c;建议开发者具备TS语…

docker小白第十一天

docker小白第十一天 dockerfile分析 Dockerfile是用来构建Docker镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚本。即构建新镜像时会用到。 构建三步骤&#xff1a;编写dockerfile文件-docker build命令构建镜像-docker run镜像 运行容器实例。即一…

Win32 基本程序设计原理总结

目录 1. Windows系统 基本原理 2. 需要什么函数库&#xff08;.LIB&#xff09; 2.1 C Runtimes&#xff1a; 2.2 Windows API 3. 需要什么头文件&#xff08;.H&#xff09; 4. Windows 程序运行的本质 5. 窗口类的注册与窗口的诞生 6.消息 6.1 消息分类&#xff1a;…

Vue3 结合typescript 组合式函数(2)

安装axios&#xff1a;npm install axios 1、hooks文件夹下新建useURLLoader 在APP.VUE中使用useURLLoader 使用Dog API 2、使用对象中的属性&#xff0c;必须使用toRefs&#xff0c;否则Reactive响应失效 3、使用泛型 结果&#xff1a;

VS2022 Android NativeActivity 开发指南

几年前最初使用VS时&#xff0c;记得是有Android NativeActivity的&#xff0c;今天更新到了2022最新版&#xff0c;发现找不到这个创建选项。 然后确保安装了C 跨平台开发工具后&#xff0c;开始排查原因。 Visual Studio 2022 中没有“本机活动应用程序” - android - SO中…