基于Spring事件驱动模式实现业务解耦
创始人
2024-06-01 01:14:12
0

事件驱动模式

举个例子🌰

大部分软件或者APP都有会有会员系统,当我们注册为会员时,商家一般会把我们拉入会员群、给我们发优惠券、推送欢迎语什么的。
在这里插入图片描述
值得注意的是:

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

传统写法

public Boolean doRegisterVip(){//1、注册会员registerVip();//2、入会员群joinMembershipGroup();//3、发优惠券issueCoupons();//4、推送消息sendWelcomeMsg();
}

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

事件驱动模式原理介绍🍓

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

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

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

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

原理图如下:
在这里插入图片描述

代码实现

1. 定义基本元素

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

package com.example.event.config;/*** 事件引擎*/
public interface EventEngine {/*** 发送事件** @param event 事件*/void publishEvent(BizEvent event);
}
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> bizSubscribers = new HashMap<>(16);@Overridepublic void publishEvent(BizEvent event) {List 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 bizListenerExecutor.** @param bizListenerExecutor value to be assigned to property bizListenerExecutor*/public void setBizListenerExecutor(Executor bizListenerExecutor) {this.bizListenerExecutor = bizListenerExecutor;}/*** Setter method for property bizSubscribers.** @param bizSubscribers value to be assigned to property bizSubscribers*/public void setBizSubscribers(Map> bizSubscribers) {this.bizSubscribers = bizSubscribers;}/*** Setter method for property async.** @param async value to be assigned to property async*/public void setAsync(boolean async) {this.async = async;}
}

事件:BizEvent.java

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 topic.** @return property value of topic*/public String getTopic() {return topic;}/*** Getter method for property id.** @return property value of id*/public String getBizId() {return bizId;}/*** Getter method for property data.** @return property value of data*/public Object getData() {return data;}
}

事件监听者:EventSubscriber.java

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;}@Overridepublic void run() {bizEventListener.onEvent(bizEvent);}
}

2. 其他组件

业务事件监听器:BizEventListener.java

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

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";}

3. 监听器实现

优惠券处理器:CouponsHandlerListener.java

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 {@Overridepublic boolean decide(BizEvent event) {return true;}@Overridepublic void onEvent(BizEvent event) {System.out.println("优惠券处理器:十折优惠券已发放");}
}

会员群处理器:MembershipHandlerListener.java

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 {@Overridepublic boolean decide(BizEvent event) {return true;}@Overridepublic void onEvent(BizEvent event) {System.out.println("会员群处理器:您已成功加入会员群");}
}

消息推送处理器:MsgHandlerListener.java

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 {@Overridepublic boolean decide(BizEvent event) {return true;}@Overridepublic void onEvent(BizEvent event) {System.out.println("消息推送处理器:欢迎成为会员!!!");}
}

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

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> 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;}
}

4. 测试类

TestController.java

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 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

在这里插入图片描述

相关内容

热门资讯

求经典台词和经典旁白 求经典台词和经典旁白谁有霹雳布袋戏里的经典对白和经典旁白啊?朋友,你尝过失去的滋味吗? 很多人在即将...
小王子第二章主要内容概括 小王子第二章主要内容概括小王子第二章主要内容概括小王子第二章主要内容概括
爱情睡醒了第15集里刘小贝和项... 爱情睡醒了第15集里刘小贝和项天骐跳舞时唱的那首歌是什么谢谢开始找舞伴的时候是林俊杰的《背对背拥抱》...
世界是什么?世界是什么概念?可... 世界是什么?世界是什么概念?可以干什么?物质的和意识的 除了我们生活的地方 比方说山 河 公路 ...
全职猎人中小杰和奇牙拿一集被抓 全职猎人中小杰和奇牙拿一集被抓动画片是第五十九集,五十八集被发现,五十九被带回基地,六十逃走
“不周山”意思是什么 “不周山”意思是什么快快快快......一座山,神话里被共工撞倒了。
《揭秘》一元一分15张跑得快群... 一元一分麻将群加群主微【ab120590】【tj525555】 【mj120590】等风也等你。喜欢...
玩家必看手机正规红中麻将群@2... 好运连连,全网推荐:(ab120590)(mj120590)【tj525555】-Q号:(QQ443...
始作俑者15张跑的快群@24小... 微信一元麻将群群主微【ab120590】 【tj525555】【mj120590】一元一分群内结算,...
《重大通知》24小时一元红中麻... 加V【ab120590】【tj525555】【mj120590】红中癞子、跑得快,等等,加不上微信就...
盘点一下正规一块红中麻将群@2... 一元一分麻将群加群主微:微【ab120590】 【mj120590】【tj525555】喜欢手机上打...
(免押金)上下分一元一分麻将群... 微【ab120590】 【mj120590】【tj525555】专业麻将群三年房费全网最低,APP苹...
[解读]正规红中麻将跑的快@群... 微信一元麻将群群主微【ab120590】 【tj525555】【mj120590】一元一分群内结算,...
《普及一下》全天24小时红中... 微【ab120590】 【mj120590】【tj525555】专业麻将群三年房费全网最低,APP苹...
优酷视频一元一分正规红中麻将... 好运连连,全网推荐:(ab120590)(mj120590)【tj525555】-Q号:(QQ443...
《火爆》加入附近红中麻将群@(... 群主微【ab120590】 【mj120590】【tj525555】免带押进群,群内跑包包赔支持验证...
《字节跳动》哪里有一元一分红中... 1.进群方式-[ab120590]或者《mj120590》【tj525555】--QQ(QQ4434...
全网普及红中癞子麻将群@202... 好运连连,全网推荐:(ab120590)(mj120590)【tj525555】-Q号:(QQ443...
「独家解读」一元一分麻将群哪里... 1.进群方式《ab120590》或者《mj120590》《tj525555》--QQ(4434063...
通知24小时不熄火跑的快群@2... 1.进群方式《ab120590》或者《mj120590》《tj525555》--QQ(4434063...