首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >实现业务代码解耦:Spring事件驱动模式用起来真的优雅!

实现业务代码解耦:Spring事件驱动模式用起来真的优雅!

作者头像
良月柒
发布于 2023-12-13 04:47:59
发布于 2023-12-13 04:47:59
2.4K13
代码可运行
举报
运行总次数:3
代码可运行

程序员的成长之路

互联网/程序员/技术/资料共享

阅读本文大概需要 6 分钟。

来自:juejin.cn/post/7301910992320479284

举个例子

大部分软件或者APP都有会有会员系统,当我们注册为会员时,商家一般会把我们拉入会员群、给我们发优惠券、推送欢迎语什么的。

值得注意的是:

  1. 注册成功后才会产生后面的这些动作;
  2. 注册成功后的这些动作没有先后执行顺序之分;
  3. 注册成功后的这些动作的执行结果不能互相影响;

传统写法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public Boolean doRegisterVip(){
 //1、注册会员
 registerVip();
 //2、入会员群
 joinMembershipGroup();
 //3、发优惠券
 issueCoupons();
 //4、推送消息
 sendWelcomeMsg();
}

这样的写法将所有的动作都耦合在doRegisterVip方法中,首先执行效率低下,其次耦合度太高,最后不好扩展。那么如何优化这种逻辑呢?

事件驱动模式原理介绍

Spring的事件驱动模型由三部分组成:

事件:用户可自定义事件类和相关属性及行为来表述事件特征,Spring4.2之后定义事件不需要再显式继承ApplicationEvent类,直接定义一个bean即可,Spring会自动通过PayloadApplicationEvent来包装事件。

事件发布者:在Spring中可通过ApplicationEventPublisher把事件发布出去,这样事件内容就可以被监听者消费处理。

事件监听者:ApplicationListener,监听发布事件,处理事件发生之后的后续操作。

原理图如下:

代码实现

定义基本元素

事件发布者:EventEngine.java、EventEngineImpl.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.event.config;

/**
 * 事件引擎
 */
public interface EventEngine {

    /**
     * 发送事件
     *
     * @param event 事件
     */
    void publishEvent(BizEvent event);
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.event.config;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;

import org.springframework.util.CollectionUtils;

/**
 * 事件引擎实现类
 */
public class EventEngineImpl implements EventEngine {

    /**
     * 异步执行器。也系统需要自行定义线程池
     */
    private Executor bizListenerExecutor;

    /**
     * 是否异步,默认为false
     */
    private boolean async;

    /**
     * 订阅端 KEY是TOPIC,VALUES是监听器集合
     */
    private Map<String, List<BizEventListener>> bizSubscribers = new HashMap<>(16);

    @Override
    public void publishEvent(BizEvent event) {
        List<BizEventListener> listeners = bizSubscribers.get(event.getTopic());
        if (CollectionUtils.isEmpty(listeners)) {
            return;
        }
        for (BizEventListener bizEventListener : listeners) {
            if (bizEventListener.decide(event)) {
                //异步执行的话,放入线程池
                if (async) {
                    bizListenerExecutor.execute(new EventSubscriber(bizEventListener, event));
                } else {
                    bizEventListener.onEvent(event);
                }

            }
        }
    }

    /**
     * Setter method for property <tt>bizListenerExecutor</tt>.
     *
     * @param bizListenerExecutor value to be assigned to property bizListenerExecutor
     */
    public void setBizListenerExecutor(Executor bizListenerExecutor) {
        this.bizListenerExecutor = bizListenerExecutor;
    }

    /**
     * Setter method for property <tt>bizSubscribers</tt>.
     *
     * @param bizSubscribers value to be assigned to property bizSubscribers
     */
    public void setBizSubscribers(Map<String, List<BizEventListener>> bizSubscribers) {
        this.bizSubscribers = bizSubscribers;
    }

    /**
     * Setter method for property <tt>async</tt>.
     *
     * @param async value to be assigned to property async
     */
    public void setAsync(boolean async) {
        this.async = async;
    }
}

事件:BizEvent.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.event.config;

import java.util.EventObject;

/**
 * 业务事件
 */
public class BizEvent extends EventObject {

    /**
     * Topic
     */
    private final String topic;

    /**
     * 业务id
     */
    private final String bizId;

    /**
     * 数据
     */
    private final Object data;

    /**
     * @param topic 事件topic,用于区分事件类型
     * @param bizId 业务ID,标识这一次的调用
     * @param data  事件传输对象
     */
    public BizEvent(String topic, String bizId, Object data) {
        super(data);
        this.topic = topic;
        this.bizId = bizId;
        this.data = data;
    }

    /**
     * Getter method for property <tt>topic</tt>.
     *
     * @return property value of topic
     */
    public String getTopic() {
        return topic;
    }

    /**
     * Getter method for property <tt>id</tt>.
     *
     * @return property value of id
     */
    public String getBizId() {
        return bizId;
    }

    /**
     * Getter method for property <tt>data</tt>.
     *
     * @return property value of data
     */
    public Object getData() {
        return data;
    }
}

事件监听者:EventSubscriber.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.event.config;

/**
 * 事件监听者。注意:此时已经没有线程上下文,如果需要请修改构造函数,显示复制上下文信息
 */
public class EventSubscriber implements Runnable {

    /**
     * 业务监听器
     **/
    private BizEventListener bizEventListener;

    /**
     * 业务事件
     */
    private BizEvent bizEvent;

    /**
     * @param bizEventListener 事件监听者
     * @param bizEvent         事件
     */
    public EventSubscriber(BizEventListener bizEventListener, BizEvent bizEvent) {
        super();
        this.bizEventListener = bizEventListener;
        this.bizEvent = bizEvent;
    }

    @Override
    public void run() {
        bizEventListener.onEvent(bizEvent);
    }
}

其他组件

业务事件监听器:BizEventListener.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.event.config;

import java.util.EventListener;

/**
 * 业务事件监听器
 *
 */
public interface BizEventListener extends EventListener {

    /**
     * 是否执行事件
     *
     * @param event 事件
     * @return
     */
    public boolean decide(BizEvent event);

    /**
     * 执行事件
     *
     * @param event 事件
     */
    public void onEvent(BizEvent event);
}

事件引擎topic:EventEngineTopic.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.event.config;

/**
 * 事件引擎topic,用于区分事件类型
 */
public class EventEngineTopic {
    /**
     * 入会员群
     */
    public static final String JOIN_MEMBERSHIP_GROUP = "joinMembershipGroup";

    /**
     * 发优惠券
     */
    public static final String ISSUE_COUPONS = "issueCoupons";

    /**
     * 推送消息
     */
    public static final String SEND_WELCOME_MSG = "sendWelcomeMsg";

}

监听器实现

优惠券处理器:CouponsHandlerListener.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.event.listener;

import com.example.event.config.BizEvent;
import com.example.event.config.BizEventListener;
import org.springframework.stereotype.Component;

/**
 * 优惠券处理器
 */
@Component
public class CouponsHandlerListener implements BizEventListener {

    @Override
    public boolean decide(BizEvent event) {
        return true;
    }

    @Override
    public void onEvent(BizEvent event) {
        System.out.println("优惠券处理器:十折优惠券已发放");
    }
}

会员群处理器:MembershipHandlerListener.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.event.listener;

import com.example.event.config.BizEvent;
import com.example.event.config.BizEventListener;
import org.springframework.stereotype.Component;

/**
 * 会员群处理器
 */
@Component
public class MembershipHandlerListener implements BizEventListener {
    @Override
    public boolean decide(BizEvent event) {
        return true;
    }

    @Override
    public void onEvent(BizEvent event) {
        System.out.println("会员群处理器:您已成功加入会员群");
    }
}

消息推送处理器:MsgHandlerListener.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.event.listener;

import com.example.event.config.BizEvent;
import com.example.event.config.BizEventListener;
import org.springframework.stereotype.Component;

/**
 * 消息推送处理器
 */
@Component
public class MsgHandlerListener implements BizEventListener {

    @Override
    public boolean decide(BizEvent event) {
        return true;
    }

    @Override
    public void onEvent(BizEvent event) {
        System.out.println("消息推送处理器:欢迎成为会员!!!");
    }
}

事件驱动引擎配置:EventEngineConfig.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.event.listener;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.example.event.config.BizEventListener;
import com.example.event.config.EventEngine;
import com.example.event.config.EventEngineImpl;
import com.example.event.config.EventEngineTopic;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;

/**
 * 事件驱动引擎配置
 */
@Configuration
public class EventEngineConfig {
    /**
     * 线程池异步处理事件
     */
    private static final Executor EXECUTOR = new ThreadPoolExecutor(20, 50, 10, TimeUnit.MINUTES,
        new LinkedBlockingQueue(500), new CustomizableThreadFactory("EVENT_ENGINE_POOL"));

    @Bean("eventEngineJob")
    public EventEngine initJobEngine(CouponsHandlerListener couponsHandlerListener,
        MembershipHandlerListener membershipHandlerListener,
        MsgHandlerListener msgHandlerListener) {
        Map<String, List<BizEventListener>> bizEvenListenerMap = new HashMap<>();
        //注册优惠券事件
        bizEvenListenerMap.put(EventEngineTopic.ISSUE_COUPONS, Arrays.asList(couponsHandlerListener));
        //注册会员群事件
        bizEvenListenerMap.put(EventEngineTopic.JOIN_MEMBERSHIP_GROUP, Arrays.asList(membershipHandlerListener));
        //注册消息推送事件
        bizEvenListenerMap.put(EventEngineTopic.SEND_WELCOME_MSG, Arrays.asList(msgHandlerListener));

        EventEngineImpl eventEngine = new EventEngineImpl();
        eventEngine.setBizSubscribers(bizEvenListenerMap);
        eventEngine.setAsync(true);
        eventEngine.setBizListenerExecutor(EXECUTOR);
        return eventEngine;
    }
}

测试类

TestController.java

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.example.event.controller;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.annotation.Resource;

import com.example.event.config.BizEvent;
import com.example.event.config.EventEngine;
import com.example.event.config.EventEngineTopic;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试
 */
@RestController
@RequestMapping("/test")
public class TestController {

    @Resource(name = "eventEngineJob")
    private EventEngine eventEngine;

    @GetMapping("/doRegisterVip")
    public String doRegisterVip(@RequestParam(required = true) String userName,
        @RequestParam(required = true) Integer age) {
        Map<String, Object> paramMap = new HashMap<>(16);
        paramMap.put("userName", userName);
        paramMap.put("age", age);
        //1、注册会员,这里不实现了
        System.out.println("注册会员成功");
        //2、入会员群
        eventEngine.publishEvent(
            new BizEvent(EventEngineTopic.JOIN_MEMBERSHIP_GROUP, UUID.randomUUID().toString(), paramMap));
        //3、发优惠券
        eventEngine.publishEvent(
            new BizEvent(EventEngineTopic.ISSUE_COUPONS, UUID.randomUUID().toString(), paramMap));
        //4、推送消息
        eventEngine.publishEvent(
            new BizEvent(EventEngineTopic.SEND_WELCOME_MSG, UUID.randomUUID().toString(), paramMap));
        return "注册会员成功";
    }
}

项目代码结构

调用接口

http://localhost:8080/test/doRegisterVip?userName=zhangsan&age=28

<END>

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
内容包含Java基础、JavaWeb、MySQL性能优化、JVM、锁、百万并发、消息队列、高性能缓存、反射、Spring全家桶原理、微服务、Zookeeper......等技术栈!
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-12-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员的成长之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
1 条评论
热度
最新
如果其中某个事件抛异常了,其他会回滚吗?
如果其中某个事件抛异常了,其他会回滚吗?
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
使用Spring Event解耦业务开发
Spring 事件是观察者模式的一种体现,对象间的一对多关系,被观察者发出信号时候会通知监听该事件的观察者;而发布-订阅模型往往需要一个调度中心,如消息队列等
itliusir
2018/10/08
1.1K0
使用Spring Event解耦业务开发
面试了个30岁的程序员,让我莫名其妙的开始慌了
我:使用事件的模式可以对系统进行解耦,事件源发布一个事件,事件监听器可以消费这个事件,而事件源不用关注发布的事件有哪些监听器,这可以可以对系统进行解耦
马士兵的朋友圈
2020/07/31
1.1K0
面试了个30岁的程序员,让我莫名其妙的开始慌了
spring的事件驱动有时候比消息队列好用
在现代开发中,异步处理越来越受到青睐。常见方案是通过消息队列实现异步通信,但Spring Boot的事件驱动模型提供了一种更轻量级的选择。本文将带你了解事件驱动的基本原理及其应用场景。
一只牛博
2025/05/30
2230
spring的事件驱动有时候比消息队列好用
优雅!Spring Boot 3.3 实现职责链模式,轻松应对电商订单流程
在电商系统中,订单的处理流程通常涉及多个步骤,每个步骤都可能有不同的业务逻辑。例如,当用户提交订单时,系统需要校验库存、验证优惠券、计算运费、处理支付、分配物流等。这些操作看似独立,但实际上具有一定的顺序依赖性。为了更好地管理这些业务逻辑,我们需要将这些流程模块化,并按需执行。
程序员皮皮林
2024/10/16
5310
优雅!Spring Boot 3.3 实现职责链模式,轻松应对电商订单流程
微服务架构设计之解耦合
在各个 IT 行业的公司,我们会有大大小小的业务需求。当每个产品的业务功能越来越繁重时,也许用户的需求其实很简单,就想 One Click。但是,其实这一个按钮背后可能有很多的系统交互的操作在进行,这就涉及到业务数据操作的事务,涉及到每个系统的交互逻辑、先后顺序以及数据的一致性。这些都需要在设计的时候,需要考虑到的问题。
程序猿Damon
2020/05/25
1.2K0
【玩转腾讯云】事件驱动编程
我们大部分人的编程习惯都是线性编程,所谓线性编程就是一个请求涉及到A,B,C,D等n个有顺序关系的操作在编码处理层面都是顺序性的,这样会导致随着业务的发展,依赖A操作结果的业务越来越多,请求处理会出现A->B->C->D->E....等很多个操作和A操作耦合在一起,会直接导致接口的rt变高,另外业务层面边界变得模糊,各个业务线的逻辑相互穿插,相互强依赖.
叔牙
2021/04/15
2.5K1
Spring源码-监听事件ApplicationListener和ApplicationEvent源码分析
 Spring源码-监听事件ApplicationListener和ApplicationEvent源码分析 Spring中ApplicationListener和ApplicationEvent是典型的事件驱动模型,也就是我们常说的发布-订阅模型 。其实我们在开发中是经常用到这种发布-订阅模型模型的,发布订阅模型一般用在一对多的对象关系上,比如如下案例中,我们就能用到这种发布-订阅模型。 案例:在用户注册成功后,往往还需要做其他事。 1、加积分 2、发确认邮件 3、如果是游戏帐户,可能赠送游戏大礼包 4、
秋日芒草
2018/05/15
9920
Spring中的事件
文章目录 1. 简介 2. 事件 2.1. Spring中内置的事件 2.2. 自定义事件 3. 监听器 3.1. 实现ApplicationListener接口 3.2. 使用@EventListener注解 4. 事件发布 4.1. Spring的事件发布类 4.2. 直接注入 4.3. 使用ApplicationEventPublisherAware注入 5. 事件多播器 6. 异步事件 6.1. 使用@Async实现异步 6.2. 自定义事件多播器 7. 源码解析 简介 学过编程语言的肯定知道事
爱撒谎的男孩
2019/12/31
1.6K0
Spring源码浅析——事件和异步事件
观察者模式(Observer Pattern)是一种设计模式,用于在对象之间定义一种一对多的依赖关系,以便当一个对象的状态发生变化时,所有依赖于它的其他对象都能够自动接收通知并做出相应的处理。
用户1413827
2023/11/28
6060
你还在写垃圾代码?快用 Java 8 重构传统设计模式吧,是真的优雅!
点击关注公众号,Java干货及时送达 来源:https://www.cnblogs.com/yjmyzz/p/refactor-design-pattern-using-java8.html java8中提供的很多新特性可以用来重构传统设计模式中的写法,下面是一些示例: 一、策略模式 上图是策略模式的类图,假设我们现在要保存订单,OrderService接口定义要做什么,而NoSqlSaveOrderStragegy以及MySqlSaveOrderStrategy则提供了二种策略,分别是保存到nosq
Java技术栈
2022/08/25
2610
你还在写垃圾代码?快用 Java 8 重构传统设计模式吧,是真的优雅!
DDD落地之事件驱动模型
周末的时候写了一文带你落地DDD,发现大家对于新的领域与知识都挺感兴趣的。后面将会出几篇DDD系列文章给大家介绍mvc迁移DDD实际要做的一些步骤。
柏炎
2022/08/23
1.2K0
DDD落地之事件驱动模型
业务解耦-事件监听模式的使用
事件监听机制有点类似于sub/pub模式,不过这个技术点也仅适用于单体应用的范围,分布式应用还是老老实实使用消息队列来进行吧。
码农王同学
2020/03/25
5120
Spring5源码 - 12 Spring事件监听机制_异步事件监听应用及源码解析
Spring提供的事件机制,默认是同步的。如果想要使用异步事件监听,可以自己实现ApplicationEventMulticaster接口,并在Spring容器中注册id为applicationEventMulticaster的Bean , 设置 executor 。
小小工匠
2021/08/17
1.1K0
如何做到业务优雅解耦?
在现代应用程序中,各个组件之间的通信是至关重要的。想象一下,你的应用程序中的各个模块像是一个巨大的交响乐团,每个模块都是一位音乐家,而Spring事件机制就像是指挥家,将所有音乐家协调得天衣无缝。
架构狂人
2024/03/06
2930
如何做到业务优雅解耦?
高级码农设计的程序能解耦,是多么重要的一件事情!
摔杯为号、看我眼色行事、见南面火起,这是在嘎哈么?这其实是在通过事物传播进行解耦引线和炸弹,仅仅是这样的一个解耦,它放到了多少村夫莽汉,劫了法场,篡了兵权!
小傅哥
2021/07/08
6740
高级码农设计的程序能解耦,是多么重要的一件事情!
Spring Event 业务解耦神器,刷爆了
点击上方“芋道源码”,选择“设为星标” 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java 2021 超神之路,很肝~ 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网络应用框架 Netty 源码解析 消息中间件 RocketMQ 源码解析 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析 作业调度中间件 Elastic-Job 源码解析 分布式事务中间件 TCC-Transaction
芋道源码
2022/07/26
7900
Spring Event 业务解耦神器,刷爆了
深度解析Spring事件机制:基于观察者模式的解耦利器
在Spring框架的生态系统中,事件机制作为模块间通信的神经脉络,扮演着至关重要的角色。这套基于观察者模式构建的体系,通过ApplicationEvent、ApplicationEventPublisher和ApplicationListener三大核心组件,实现了业务逻辑的解耦与高效协作。截至2025年,这套机制仍然是Spring框架中最优雅的组件通信解决方案之一。
用户6320865
2025/08/27
3470
深度解析Spring事件机制:基于观察者模式的解耦利器
SpringBoot事件监听机制及观察者模式/发布订阅模式
在GoF的《设计模式》中,观察者模式的定义:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。如果你觉得比较抽象,接下来这个例子应该会让你有所感觉:
烂猪皮
2023/09/04
1.2K0
SpringBoot事件监听机制及观察者模式/发布订阅模式
Spring中的事件驱动模型(一)
事件驱动模型 事件驱动模型通常也被理解成观察者或者发布/订阅模型。 是一种对象间的一对多的关系; 当目标发送改变(发布),观察者(订阅者)就可以接收到改变; 观察者如何处理,目标无需干涉,它们之间的
aoho求索
2018/04/03
2K0
Spring中的事件驱动模型(一)
不知道怎么解耦业务?Spring Event 了解一下!
点击上方“芋道源码”,选择“设为星标” 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java 2021 超神之路,很肝~ 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网络应用框架 Netty 源码解析 消息中间件 RocketMQ 源码解析 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析 作业调度中间件 Elastic-Job 源码解析 分布式事务中间件 TCC-Transaction
芋道源码
2022/06/29
3560
不知道怎么解耦业务?Spring Event 了解一下!
推荐阅读
相关推荐
使用Spring Event解耦业务开发
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档