事件驱动模式

举个例子

大部分软件或者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<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

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

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

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 { @Override
public boolean decide(BizEvent event) {
return true;
} @Override
public 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 {
@Override
public boolean decide(BizEvent event) {
return true;
} @Override
public 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 { @Override
public boolean decide(BizEvent event) {
return true;
} @Override
public 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<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;
}
}

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

基于Spring事件驱动模式实现业务解耦的更多相关文章

  1. 基于Spring的发布订阅模式 EventListener

    基于Spring的发布订阅模式 在我们使用spring开发应用时,经常会碰到要去解耦合一些依赖调用,比如我们在做代码的发布流程中,需要去通知相关的测试,开发人员关注发布中的错误信息.而且通知这个操作又 ...

  2. Spring中如何使用工厂模式实现程序解耦?

    目录 1. 啥是耦合.解耦? 2. jdbc程序进行解耦 3.传统dao.service.controller的程序耦合性 4.使用工厂模式实现解耦 5.工厂模式改进 6.结语 @ 1. 啥是耦合.解 ...

  3. 基于Spring实现策略模式

    背景: 看多很多策略模式,总结下来实现原理大体都差不多,在这里主要是讲解下自己基于Spring更优雅的实现方案:这个方案主要是看了一些开源rpc和Spring相关源码后的一些思路,所以在此进行总结 首 ...

  4. 我的自定义框架 || 基于Spring Boot || 第一步

    今天在园子里面看到一位大神写的springboot做的框架,感觉挺不错,遂想起来自己还没有一个属于自己的框架,决定先将大神做好的拿过来,然后加入自己觉得需要的模块,不断完善 目前直接复制粘贴过来的,后 ...

  5. 干货|基于 Spring Cloud 的微服务落地

    转自 微服务架构模式的核心在于如何识别服务的边界,设计出合理的微服务.但如果要将微服务架构运用到生产项目上,并且能够发挥该架构模式的重要作用,则需要微服务框架的支持. 在Java生态圈,目前使用较多的 ...

  6. 基于Spring Cloud的微服务落地

    微服务架构模式的核心在于如何识别服务的边界,设计出合理的微服务.但如果要将微服务架构运用到生产项目上,并且能够发挥该架构模式的重要作用,则需要微服务框架的支持. 在Java生态圈,目前使用较多的微服务 ...

  7. Spring实战5:基于Spring构建Web应用

    主要内容 将web请求映射到Spring控制器 绑定form参数 验证表单提交的参数 对于很多Java程序员来说,他们的主要工作就是开发Web应用,如果你也在做这样的工作,那么你一定会了解到构建这类系 ...

  8. Spring mvc 模式小结

    http://www.taobaotesting.com/blogs/2375 1.spring mvc简介 Spring MVC框架是一个MVC框架,通过实现Model-View-Controlle ...

  9. 基于Spring的Web缓存

    缓存的基本思想其实是以空间换时间.我们知道,IO的读写速度相对内存来说是非常比较慢的,通常一个web应用的瓶颈就出现在磁盘IO的读写上.那么,如果我们在内存中建立一个存储区,将数据缓存起来,当浏览器端 ...

  10. 基于Spring Cloud和Netflix OSS 构建微服务-Part 1

    前一篇文章<微服务操作模型>中,我们定义了微服务使用的操作模型.这篇文章中,我们将开始使用Spring Cloud和Netflix OSS实现这一模型,包含核心部分:服务发现(Servic ...

随机推荐

  1. 深入浅出MySQL事务

    Photo by Lukas Hartmann from Pexels 辞职这段时间以来看见了很多工作之外的东西,我认为这是值得的.同时也有时间和机会来好好整理所学所想,准备开启下一段旅途. 事务的定 ...

  2. React组件三大属性state,props,refs

    1. React组件定义 1.1 函数组件(Function Components) 函数组件是一种简单的定义组件的方式,通过一个JavaScript函数来定义组件.函数接收一个props对象作为参数 ...

  3. CDMP国际数据治理认证训练营来了(7-8月)

    大家好,我是独孤风,一位曾经的港口煤炭工人,目前在某国企任大数据负责人,公众号大数据流动主理人.在最近的两年的时间里,因为公司的需求,还有大数据的发展趋势所在,我开始学习数据治理的相关知识. 经过一段 ...

  4. 大数据面试题集锦-Hadoop面试题(四)-YARN

    你准备好面试了吗?这里有一些面试中可能会问到的问题以及相对应的答案.如果你需要更多的面试经验和面试题,关注一下"张飞的猪大数据分享"吧,公众号会不定时的分享相关的知识和资料. 目录 ...

  5. AI技术在软件测试中的应用和实践

    随着人工智能(AI)技术的快速发展,它在各个领域都展现出了巨大的潜力和影响力.在软件测试领域,AI技术也越来越得到重视和应用.本文将探讨AI技术在软件测试中的应用和实践,重点关注chatGPT如何根据 ...

  6. Day07_Java_作业

    A:简答题 1. 构造方法的作用是什么?构造方法的特点是什么?构造方法的注意事项? 构造方法中可不可以写return语句呢? 2. 一个类的缺省构造方法可以有参数吗? 3. 静态代码块和构造代码块的特 ...

  7. Java通用返回工具类Result

    通用返回类Result 前言:Java项目搭建时,常常需要去封装一个通用型的Result工具类,下面就是我自己封装的常用的返回类,可以直接使用.(有部分Swagger注解,使用时可忽略) 第一步.创建 ...

  8. DevOps|研发效能治理:进化史、规模化与治理复杂性

    麻广广@码猿外 研发效能这个词近几年火遍全网,各大企业都加入了研发效能治理的行列,开始梳理企业内部各个团队的研发流程,以期望找到企业降本增效的方向. 抛开政治因素,研发效能治理我们到底是在谈什么呢?从 ...

  9. 代码随想录算法训练营第三天| LeetCode 203.移除链表元素(同时也对整个单链表进行增删改查操作) 707.设计链表 206.反转链表

    203.移除链表元素         题目链接/文章讲解/视频讲解::https://programmercarl.com/0203.%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1 ...

  10. #Powerbi 1分钟学会利用AI,为powerbi报表进行高端颜色设计

    在BI报表的设计中,配色方案往往成为一大难题,一组切合主题.搭配合理的颜色设计往往能为我们的报表,加分不少. 今天,就介绍一个AI配色的网站,利用AI为pbi报表进行配色设计. 一:网站网址 http ...