责任链模式与spring容器的搭配应用

背景

有个需求,原先只涉及到一种A情况设备的筛选,每次筛选会经过多个流程,比如先a功能,a功能通过再筛选b功能,然后再筛选c功能,以此类推。现在新增了另外一种B情况的筛选,B情况同样需要A情况的筛选流程,并且需要在A情况的基础上,新增另外的功能筛选,这里假设A需要a、b、c功能的筛选,而B需要a、b、c、d功能的筛选,并且这些功能的筛选的顺序可能发生变动,比如新增了某个筛选,这个筛选涉及到的计算量少那肯定可以把这个置在前面先处理,不满足条件就return,咋一看,这个需求很符合责任链模式的应用场景,下面介绍编码。这里的代码参考了 马丁玩编程 在其12306项目里面的责任链模式,并做出一些相应改动,以适配当前的场景。

代码

责任链模式顶层接口

这里继承了Ordered类,是为了方便后续对处理器进行排序。

public interface AbstractChainHandler<REQUEST> extends Ordered {default boolean handler(REQUEST requestParam){return true;};}

A情况的接口和B情况的接口。

public interface DeviceTypeAChainFilter extends AbstractChainHandler<DeviceFilterBO> {}

public interface DeviceTypeBChainFilter extends AbstractChainHandler<DeviceFilterBO> {}

定义成接口,后续往里面添加处理器的时候,方便查看当前A规则和B规则都有哪些处理器:

image-20240206171639069

具体的处理器

处理器1:

@Component
public class DeviceFunctionChainHandler implements DeviceTypeAChainFilter, DeviceTypeBChainFilter {@Overridepublic boolean handler(DeviceFilterBO deviceFilterBO) {if (deviceFilterBO.getDeviceBO().getCondition() % 2 == 0) {System.out.println("处理器A:筛选功能不通过");return false;}// 筛选功能System.out.println("处理器A:筛选功能通过");return true;}@Overridepublic int getOrder() {return 0;}
}

处理器2:

@Component
public class DeviceResolutionChainHandler implements DeviceTypeAChainFilter, DeviceTypeBChainFilter {@Overridepublic boolean handler(DeviceFilterBO deviceFilterBO) {// 分辨率支持System.out.println("处理器B:分辨率支持");return true;}@Overridepublic int getOrder() {return 10;}}

处理器3:

@Component
public class DeviceCaculateOutputChainHandler implements DeviceTypeBChainFilter {@Overridepublic boolean handler(DeviceFilterBO deviceFilterBO) {// 接口支持System.out.println("处理器C:输出接口支持");// 计算设备数量满足要求System.out.println("处理器C:根据输出接口计算的设备数量满足要求");return true;}@Overridepublic int getOrder() {return 30;}
}

处理器4:

@Component
public class DeviceCaculateInputChainHandler implements DeviceTypeAChainFilter, DeviceTypeBChainFilter {@Overridepublic boolean handler(DeviceFilterBO deviceFilterBO) {if (deviceFilterBO.getDeviceBO().getCondition() % deviceFilterBO.getCondition() == 0) {System.out.println("处理器D:输入接口不支持");return false;}ArrayList<DeviceBO> deviceRes = (ArrayList<DeviceBO>) AbstractChainContext.threadLocal.get();deviceRes.add(deviceFilterBO.getDeviceBO());// 接口支持System.out.println("处理器D:输入接口支持");// 计算设备数量满足要求System.out.println("处理器D:根据输入接口计算的设备数量满足要求");return true;}@Overridepublic int getOrder() {return 40;}
}

可以看到,处理器都用@Component进行标识,后续通过ioc容器获取这些处理器进行分类和执行。并且,可以看到A..filter接口有三个实现者,这说明A有三种处理器,同理B有四种处理器,并且由于顶层接口继承了Order类,所有具体的处理器都会标识当前的order,如上面的10,20,30...这里把Order的数字间隔放大一些,比如10,20,30,如果以后要往这些间隔插入新的处理逻辑也方便。

获取具体处理器和执行hanlder的上下文类

先将不同的处理规则的接口都放在某个特定包下

image-20240206172405349

先去扫描这个包下的所有接口,然后再去Spring Ioc容器里面拿出这些接口的实现类,把不同的接口实现类按接口名字作为标识,按Order对这些实现类进行排序,然后放到一个List里面,以接口名字作为key,实现类List作为value,后续调用链式调用的时候,传入具体的接口名字(处理规则名字),实现链式顺序调用,具体实现如下

AbstractChainContext上下文类:

public final class AbstractChainContext<REQUEST, RESPONSE> implements CommandLineRunner {private final static Map<String, List<AbstractChainHandler>> abstractChainHandlerContainer = new HashMap<>();public final static ThreadLocal threadLocal = new ThreadLocal<>();public void handler(String mark, REQUEST requestParam) {List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(mark);if (CollectionUtils.isEmpty(abstractChainHandlers)) {throw new RuntimeException(String.format("[%s] Chain of Responsibility ID is undefined.", mark));}for (AbstractChainHandler abstractChainHandler : abstractChainHandlers) {if(!abstractChainHandler.handler(requestParam)){break;}}}@Overridepublic void run(String... args) {List<Class<?>> interfaces = getInterfacesInPackage("com.zh.demo.designpattern.chain.type");for (Class<?> interfaceType : interfaces) {Map<String, AbstractChainHandler> beansOfType = (Map<String, AbstractChainHandler>) ApplicationContextHolder.getBeansOfType(interfaceType);// 转成listList<AbstractChainHandler> sortedList = beansOfType.values().stream().sorted(Comparator.comparing(Ordered::getOrder)).collect(Collectors.toList());int index = interfaceType.getName().lastIndexOf(".") + 1;abstractChainHandlerContainer.put(interfaceType.getName().substring(index), sortedList);}}public static List<Class<?>> getInterfacesInPackage(String packageName) {List<Class<?>> result = new ArrayList<>();try {ClassLoader classLoader = Thread.currentThread().getContextClassLoader();String path = packageName.replace('.', '/');Enumeration<URL> resources = classLoader.getResources(path);while (resources.hasMoreElements()) {URL resource = resources.nextElement();File directory = new File(resource.getFile());File[] files = directory.listFiles();if (files != null) {for (File file : files) {if (file.getName().endsWith(".class")) {String className = packageName + '.' + file.getName().replace(".class", "");Class<?> clazz = Class.forName(className);if (clazz.isInterface()) {result.add(clazz);}}}}}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}return result;}
}

在上面变量中,用了个 public final static ThreadLocal threadLocal = new ThreadLocal<>(); 这个是用来保存设备的筛选列表。

定义好不同筛选规则的枚举类:

public enum DeviceChainMarkEnum {/*** A设备过滤器*/DEVICE_TYPEA_FILTER("DeviceTypeAChainFilter"),/*** B设备过滤器*/DEVICE_TYPEB_FILTER("DeviceTypeBChainFilter");String name;public String getName() {return name;}DeviceChainMarkEnum(String name) {this.name = name;}}

Service的编写

@Service
@RequiredArgsConstructor
@Slf4j
public class DemoServiceImpl implements DemoService {private final AbstractChainContext<DeviceFilterBO, Object> devcieTypeChainContext;@Overridepublic List<DeviceBO> filterDeviceTypeA(ParmDTO parmDTO) {ArrayList<DeviceBO> deviceList = new ArrayList<>();// 简化条件parmDTO.setCondition(2);// 实际情况应该是从数据库读取设备的信息for (int i = 0; i < 5; i++) {DeviceBO deviceDTO = DeviceBO.builder().condition(new Random().nextInt(100)).build();deviceList.add(deviceDTO);}ArrayList<DeviceBO> deviceRes = new ArrayList<>();// 把需要的结果放到threadLocal中,在具体的处理器中对结果List进行处理AbstractChainContext.threadLocal.set(deviceRes);// 筛选多个设备 对符合的设备加入到deviceResfor (DeviceBO deviceBo : deviceList) {DeviceFilterBO deviceFilterBO = DeviceFilterBO.builder().condition(parmDTO.getCondition()).deviceBO(deviceBo).build();// 以A规则进行处理devcieTypeChainContext.handler(DeviceChainMarkEnum.DEVICE_TYPEA_FILTER.getName(), deviceFilterBO);}AbstractChainContext.threadLocal.remove();System.out.println("筛选结果数量:" + deviceRes.size());return deviceRes;}@Overridepublic List<DeviceBO> filterDeviceTypeB(ParmDTO parmDTO) {ArrayList<DeviceBO> deviceList = new ArrayList<>();// 简化条件parmDTO.setCondition(2);// 实际情况应该是从数据库读取设备的信息for (int i = 0; i < 5; i++) {DeviceBO deviceDTO = DeviceBO.builder().condition(new Random().nextInt(100)).build();deviceList.add(deviceDTO);}ArrayList<DeviceBO> deviceRes = new ArrayList<>();// 把需要的结果放到threadLocal中,在具体的处理器中对结果List进行处理AbstractChainContext.threadLocal.set(deviceRes);// 筛选多个设备 对符合的设备加入到deviceResfor (DeviceBO deviceBo : deviceList) {DeviceFilterBO deviceFilterBO = DeviceFilterBO.builder().condition(parmDTO.getCondition()).deviceBO(deviceBo).build();// 以B规则进行处理devcieTypeChainContext.handler(DeviceChainMarkEnum.DEVICE_TYPEB_FILTER.getName(), deviceFilterBO);}AbstractChainContext.threadLocal.remove();System.out.println("筛选结果数量:" + deviceRes.size());return deviceRes;}}

这里假设有五种设备,每个设备通过DeviceBO里面的condition设置条件,演示一遍筛选过程

DeviceBO类:

@Builder
@Data
public class DeviceBO {private int condition;}

演示筛选规则A,一共五个设备数据,只有一个筛选通过了,这里涉及到A,B,D三种处理器

image-20240222103239661

演示筛选规则B,一共五个设备数据,2个筛选通过了,这里涉及到A,B,C,D三种处理器

image-20240222103338406

文章转载自:John-zh

原文链接:https://www.cnblogs.com/Johnyzh/p/18026938

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

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

相关文章

算法学习(十一)拓扑排序

拓扑排序 1. 概念 对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序&#xff0c;是将G中所有顶点排成一个线性序列&#xff0c;使得图中任意一对顶点u和v&#xff0c;若边<u,v>∈E(G)&#xff0c;则u在线性序列中出现在v之前。通常&#xff0c;这样的线性…

【Java程序员面试专栏 数据结构】三 高频面试算法题:栈和队列

一轮的算法训练完成后,对相关的题目有了一个初步理解了,接下来进行专题训练,以下这些题目就是汇总的高频题目,因为栈和队列这两哥们结构特性比较向对应,所以放到一篇Blog中集中练习 题目题干直接给出对应博客链接,这里只给出简单思路、代码实现、复杂度分析 题目关键字…

ChatGPT助你成功求职:智能引导下的职场新起点【文章底部添加可得内推码汇总表】

在当今竞争激烈的就业市场中&#xff0c;如何有效地进行求职已成为许多人面临的挑战。然而&#xff0c;随着人工智能的不断发展&#xff0c;ChatGPT作为一种强大的语言模型&#xff0c;不仅可以为我们提供信息&#xff0c;还可以成为求职过程中的得力助手。在这篇文章中&#x…

每日一学—由面试题“Redis 是否为单线程”引发的思考

文章目录 &#x1f4cb; 前言&#x1f330; 举个例子&#x1f3af; 什么是 Redis&#xff08;知识点补充&#xff09;&#x1f3af; Redis 中的多线程&#x1f3af; I/O 多线程&#x1f3af; Redis 中的多进程&#x1f4dd; 结论&#x1f3af;书籍推荐&#x1f525;参与方式 &a…

K线实战分析系列之五:刺透形态——多方反攻信号

K线实战分析系列之五&#xff1a;刺透形态——多方反攻信号 一、刺透形态二、类似刺透形态三、刺透形态的总结 一、刺透形态 阴线在前&#xff0c;阳线在后显示市场曾经跌到了低位&#xff0c;但是在盘中又将价格收回&#xff0c;并且多方收复了前一天大部分的失地 二、类似刺…

[SUCTF 2019]EasySQL1 题目分析与详解

一、题目介绍 1、题目来源&#xff1a; BUUCTF网站&#xff0c;网址&#xff1a;https://buuoj.cn/challenges 2、题目描述&#xff1a; 通过以上信息&#xff0c;拿到flag。 二、解题思路 首先打开靶机&#xff0c;尝试输入1查看回显&#xff0c;回显如图所示&#xff1a;…

利用psutil库检查脚本是否在运行

摘要 如果要判断某一脚本是否在运行&#xff0c;可以通过psutil库获取所有进程的cmdline&#xff0c;并判断指定的文件名是否在cmdline中。 目录 1.psutil库简介 2.检查代码及说明 2.1检查思路 2.2异常捕获 2.3执行方法 1.psutil库简介 psutil 是一个跨平台&#xff08;…

记一次生产jvm oom问题

前言 jvm添加以下参数&#xff0c;发生OOM时自动导出内存溢出文件 -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/opt 内存分析工具&#xff1a; MAT, 下载地址&#xff1a;Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation&#xff0c; 注意工具地址…

让AI玩一千万次贪吃蛇

如果让人工智能来玩贪吃蛇游戏&#xff0c;会发生什么&#xff1f; 图源&#xff1a;DALLE 目录 贪吃蛇实现 游戏规则 游戏实现 Q学习算法实现 Q学习简介 Q表和Q值 Q学习更新规则 Q学习在贪吃蛇游戏中的应用 整体项目完整代码 运行过程截图 代码分析 环境设置 …

2024.2.23 C++QT 作业

思维导图 练习题 1>简单实现闹钟播报&#xff0c;设置时间&#xff0c;当系统时间与设置时间相同时播报语音5次&#xff0c;然后停止。如果设置时间小于当前系统时间&#xff0c;则弹出消息提示框&#xff0c;并清空输入框。 #include "widget.h" #include &quo…

JSON(javaScript Object Notation,Js对象标记)—我耀学IT

Json是一种轻量级的数据交换格式&#xff0c;目前使用非常广泛&#xff0c;是一种轻量级的数据交换格式。易于人阅读和编写&#xff0c;可以在多种语言之间进行数据交换 。同时也易于机器解析和生成 1.1json的值: 值可以是对象、数组、数字、字符串或者三个字面值(false、nul…

什么是nginx 、安装nginx、nginx调优

一、 什么是nginx 1.1 nginx的概念 一款高新能、轻量级Web服务软件系统资源消耗低对HTTP并发连接的处理能力高单台物理服务器可支持30 000&#xff5e;50 000个并发请求。 1.2 nginx模块与作用 核心模块&#xff1a;是 Nginx 服务器正常运行必不可少的模块&#xff0c;提供错…

深入理解JS的执行上下文、词法作用域和闭包(下)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【Java程序设计】【C00262】基于Springboot的会员制医疗预约服务管理系统(有论文)

基于Springboot的会员制医疗预约服务管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的会员制医疗预约服务管理信息系统&#xff0c;本系统分为三种角色&#xff1a;管理员、医生和会员&#xff1b; 在系统…

JavaSec 基础之 XXE

文章目录 XMLReaderSAXReaderSAXBuilderDocumentBuilderUnmarshaller**SAXParserFactory**XMLReaderFactoryDigester总结 XMLReader public String XMLReader(RequestBody String content) {try {XMLReader xmlReader XMLReaderFactory.createXMLReader();// 修复&#xff1a…

如何让电脑待机而wifi不关的操作方法!!

1、一台电脑如果一天不关机&#xff0c;大约消耗0.3度电。 一般一台电脑的功耗约为250-400W&#xff08;台式机&#xff09;。 一台电脑每月的耗电量&#xff1a;如果是每小时300W每天10小时每月30天90KW&#xff0c;即90千瓦时的电。 这只是保守估计。 2、使用完毕后正常关闭…

企业微信应用开发:使用Cpolar域名配置进行本地接口回调的调试指南

文章目录 1. Windows安装Cpolar2. 创建Cpolar域名3. 创建企业微信应用4. 定义回调本地接口5. 回调和可信域名接口校验6. 设置固定Cpolar域名7. 使用固定域名校验 企业微信开发者在应用的开发测试阶段&#xff0c;应用服务通常是部署在开发环境&#xff0c;在有数据回调的开发场…

【MATLAB】 RLMD信号分解+FFT傅里叶频谱变换组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 展示出图效果 1 RLMD分解算法 RLMD&#xff08;Robust Local Mode Decomposition&#xff09;是一种鲁棒的局部模态分解方法。它是通过在局部区间内对信号进行多项式拟合&#xff0c;提取局部特征&#xff0c;进而分解信…

【Java程序设计】【C00294】基于Springboot的车辆充电桩管理系统(有论文)

基于Springboot的车辆充电桩管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的车辆充电桩管理系统 本系统前台功能模块分为&#xff1a;首页功能和用户后台管理 后台功能模块分为&#xff1a;管理员功能和…

达梦数据库搭建和连接(详解一文看懂)

达梦数据库搭建和连接 一、数据库搭建1.安装前准备2.下载 Docker3.导入安装包4.启动docker版docker-compose版 5.启动/停止数据库 二、数据库连接1、下载DBeaver2、下载驱动3、DBeaver新建驱动数据库-驱动管理器新建驱动创建新驱动设置创建驱动-库驱动类确定和关闭 4、连接 博主…