SpringBoot责任链与自定义注解:优雅解耦复杂业务

引言

责任链模式是一种行为设计模式,它允许你将请求沿着处理者链进行传递,直到有一个处理者处理请求。在实际应用中,责任链模式常用于解耦发送者和接收者,使得请求可以按照一定的规则被多个处理者依次处理。

首先,本文会通过一个实例去讲解SpringBoot使用责任链模式以及自定义注解优雅的实现一个功能。我们现在有如下图一样的一个创建订单的业务流程处理,我们选择使用责任链模式去实现。

image.png

我们分析下流程,发现从条件x开始,就分为了两条业务线,我们定义走业务节点A的叫规则A,走业务节点B的叫规则B。这样就形成了两条业务链路:

image.png

那我就开始使用自定义注解定义规则A,以及规则B。

规则注解

定义@RuleA标识处理规则A的节点:

@Qualifier  
@Target({ElementType.METHOD, ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
public @interface RuleA {  
}

定义@RuleB标识处理规则B的节点:

@Qualifier  
@Target({ElementType.METHOD, ElementType.TYPE})  
@Retention(RetentionPolicy.RUNTIME)  
public @interface RuleB {  
}

在Spring框架中,@Qualifier注解用于指定要注入的具体bean的名称。当一个接口或抽象类有多个实现类时,通过@Qualifier注解可以明确告诉Spring框架要注入哪一个实现类。

自定义注解与@Qualifier结合使用的含义在于,你可以通过自定义注解为特定的实现类分组,并在使用@Qualifier时引用这个自定义注解。这样做的主要目的是提高代码的可读性和可维护性,使得注入的意图更加清晰。

业务处理

各业务节点处理的数据是同一份,处理方法是一个,只是处理的业务不同。所以我们定义一个业务处理点的接口,让各业务节点去实现业务处理接口。

public interface INodeComponent{  
  
/**  
* 定义所有数据处理节点的接口  
* @param orderContext 数据上下文  
* @param orderParam 数据处理入参参数  
*/  
  
void handleData(OrderContext orderContext, OrderParam orderParam);  
}

然后我们实现业务处理接口:
我们定义在规则A流程中执行的节点都是用注解@RuleA去标记,如下:

@Slf4j
@Component  
@RuleA
@Order(1)
public class ANodeComponent implements INodeComponent {
	@Override  
public void handleData(OrderContext orderContext, OrderParam orderParam) {  
	log.info("RuleA流程执行处理业务节点A");  
	final List<String> executeRuleList = Optional.ofNullable(orderContext.getExecuteRuleList()).orElse(new ArrayList<>());  
	executeRuleList.add("ANodeComponent");  
	orderContext.setExecuteRuleList(executeRuleList);  
	// 不同类型订单,订单号不同,可在节点中个处理
	orderContext.setOrderId("TOC11111");
	}
}

@Slf4j  
@Component  
@RuleA  
@RuleB  
@Order(10)
public class CNodeComponent implements INodeComponent {
	// 省略具体的业务处理逻辑
}

@Slf4j  
@Component  
@RuleA  
@Order(20)
public class DNodeComponent implements INodeComponent {
	// 省略具体的业务处理逻辑
}	

@Slf4j  
@Component  
@RuleA  
@Order(30)
public class FNodeComponent implements INodeComponent {
	// 省略具体的业务处理逻辑
}

@Slf4j  
@Component  
@RuleA  
@RuleB 
@Order(40)
public class HNodeComponent implements INodeComponent {
	// 省略具体的业务处理逻辑
}

我们定义在规则B流程中执行的节点都是用注解@RuleB去标记,如下:

@Slf4j  
@Component  
@RuleB 
@Order(1)
public class BNodeComponent implements INodeComponent {
	log.info("RuleB流程执行处理业务节点B");  
	final List<String> executeRuleList = Optional.ofNullable(orderContext.getExecuteRuleList()).orElse(new ArrayList<>());  
	executeRuleList.add("BNodeComponent");
	orderContext.setExecuteRuleList(executeRuleList);  
	orderContext.setOrderId("TOB11111");
}

@Slf4j  
@Component  
@RuleA  
@RuleB  
@Order(10)
public class CNodeComponent implements INodeComponent {
	// 省略具体的业务处理逻辑
}

@Slf4j  
@Component  
@RuleB  
@Order(20)
public class ENodeComponent implements INodeComponent {
	// 省略具体的业务处理逻辑
}

@Slf4j  
@Component  
@RuleA  
@RuleB  
@Order(40)
public class HNodeComponent implements INodeComponent {
	// 省略具体的业务处理逻辑
}

可以看到如果规则A和规则B都需要执行的业务用了@RuleA@RuleB去标记。同时我们使用@Order注解定义NodeComponent的注入顺序,值越小越先注入。

基于@Order定义NodeComponent的注入顺序不是那么的友好,最好的方式是与规则注解耦合,即一个规则下定义注入顺序,

规则处理器

我们在定义条件X节点对应的针对处理规则A和规则B的处理器。
同理,因规则A以及规则B处理数据的数据是同一份,方法也是同一个,所以我们还是定义一个处理器超类:

@Slf4j  
public abstract class NodeHandler {  
  
/**  
* 处理校验订单以及创建订单信息  
* @param requestVO 订单创建入参  
* @return 订单DO实体类  
*/  
public abstract OrderDO handleOrder(OrderCreateRequestVO requestVO);  
  
/**  
* 执行业务处理链路  
* @param requestVO 订单创建入参  
* @param nodeComponentList 业务处理节点  
* @return  
*/  
protected OrderDO executeChain(OrderCreateRequestVO requestVO, List<? extends INodeComponent> nodeComponentList){
	final OrderParam orderParam = this.buildOrderParam(requestVO);  
	final OrderContext orderContext = OrderContext.builder().build();  
	for (INodeComponent nodeComponent : nodeComponentList){  
		// 此处进行业务处理节点的调用
		nodeComponent.handleData(orderContext, orderParam);  
	}  
	  
	log.info("执行的链路:{}", String.join(",", Optional.ofNullable(orderContext.getExecuteRuleList()).orElse(new ArrayList<>())));  
	return this.buildOrderDO(orderContext);
}

我们的超类对外提供统一的业务处理接口方法,同时对业务处理节点的调用进行处理的管理,对于规则处理者来说,他只需要实现handlerOrder的方法。以下是规则处理器的实现代码:

@Slf4j  
@Component("ruleA")  
public class RuleAHandler extends NodeHandler {  
  
@RuleA  
@Autowired  
private List<? extends INodeComponent> nodeComponents;  
  
	/**  
	* 处理校验订单以及创建订单信息  
	*  
	* @param requestVO 订单创建入参  
	* @return 订单DO实体类  
	*/  
	@Override  
	public OrderDO handleOrder(OrderCreateRequestVO requestVO) {  
		return super.executeChain(requestVO, nodeComponents);  
	}  
}


@Slf4j  
@Component("ruleB")  
public class RuleBHandler extends NodeHandler {  
  
	@RuleB  
	@Autowired  
	private List<? extends INodeComponent> nodeComponents;  
	  
	/**  
	* 处理校验订单以及创建订单信息  
	*  
	* @param requestVO 订单创建入参  
	* @return 订单DO实体类  
	*/  
	@Override  
	public OrderDO handleOrder(OrderCreateRequestVO requestVO) {  
		return super.executeChain(requestVO, nodeComponents);  
	}  
}

订单处理器

最后我们在创建一个订单处理器,为业务代码中提供服务接口。
先创建一个订单类型的枚举,枚举中定义使用哪个规则处理器。

@AllArgsConstructor  
public enum OrderHandlerEnum {  
  
	TO_C(1,"ruleA"),  
	TO_B(2, "ruleB");  
	  
	public final Integer orderType;  
	  
	public final String ruleHandler;  
	  
	public static String getRuleHandler(Integer orderType){  
		return Arrays.stream(OrderHandlerEnum.values()).filter(e -> Objects.equals(e.orderType, orderType)).findFirst()  
		.orElse(OrderHandlerEnum.TO_C).ruleHandler;  
	}  `
}

然后我们就可以定义一个订单处理器了,处理中决定调用那个规则处理器去执行规则。

@Slf4j  
@Component  
public class OrderFactory {  
  
	@Autowired  
	private Map<String, NodeHandler> nodeHandlerMap;  
	  
	/**  
	* 创建订单  
	* @param requestVO 订单参数  
	* @return 订单实体DO  
	*/  
	public OrderDO createOrder(OrderCreateRequestVO requestVO){  
		final Integer orderType = requestVO.getOrderType();  
		// 获取node规则执行器名称  
		final String ruleHandler = OrderHandlerEnum.getRuleHandler(orderType);  
		// 获取node规则执行器  
		final NodeHandler nodeHandler = nodeHandlerMap.get(ruleHandler);  
		if (nodeHandler == null){  
			// 异常  
			throw new RuntimeException();  
		}  
		return nodeHandler.handleOrder(requestVO);  
	}  
}

测试

我们编写测试类看一下效果:

@SpringBootTest  
public class SpringbootCodeApplicationTests {  
  
	@Autowired  
	private OrderFactory orderFactory;  
	  
	@Test  
	void testOrderCreate() {  
		final OrderCreateRequestVO requestVO = new OrderCreateRequestVO();  
		requestVO.setOrderNo("11111");  
		requestVO.setOrderType(OrderHandlerEnum.TO_C.orderType);  
		requestVO.setUserId("coderacademy");  
		requestVO.setUserName("码农Academy");  
		  
		final OrderDO orderDO = orderFactory.createOrder(requestVO);  
		System.out.println(orderDO.getOrderId());  
	  
	}  
}

执行结果日志如下:

image.png

执行结果是我们想要的。

通过采用责任链模式结合Spring Boot的优化方案,我们实现了一种高度解耦的业务逻辑处理方式。其中的主要优势在于,我们成功地将各个业务节点的处理逻辑进行解耦,使得每个节点能够独立演进,降低了代码的耦合性。

其中的最大优势体现在替换或新增业务节点处理规则时的灵活性。若需替换某一节点的处理规则,只需实现新的INodeComponent并标记相应的规则注解,系统将自动将其纳入责任链中。这意味着我们能够以最小的改动实现业务逻辑的变更,而无需涉及其他节点。

进一步地,若新增一条处理规则,只需定义新的规则注解(如@RuleC),并实现相应的INodeComponent接口,定义规则C下各节点的处理逻辑。然后,创建对应的规则C处理器即可,系统将自动将其整合到责任链中。这种设计允许我们以一种清晰、简便的方式进行代码扩展,同时使得代码接口清晰易懂,为后续维护和升级提供了便利。这种设计理念在面对日益变化的业务规则时,具有显著的适应性和可维护性。

上述示例中我们也使用了表驱动,策略模式+工厂模式,以及枚举等方式,具体请参考我另一篇的文章:代码整洁之道(一)之优化if-else的8种方案

总结

通过使用责任链模式,我们可以更优雅地组织和扩展业务逻辑。在Spring Boot中,结合自定义注解和@Qualifier注解,以及构造函数注入,可以实现更清晰、可读性更强的代码。通过控制处理者的顺序,我们可以确保责任链的执行顺序符合业务需求。

责任链模式的优雅实践使得我们的代码更具可维护性,更容易应对业务的变化。在设计和实现中,要根据实际业务场景的需要进行灵活调整,以达到最佳的解耦和可扩展性。

有的小伙伴可能也会发现我们的类定义为NodeComponent,很熟悉,是的,此类名参考一个规则引擎开源项目LiteFlow,我们下一期将会使用LiteFolw改造这个案例,由此打开学习LiteFlow的篇章,需要了解的小伙伴们注意点关注哦。。。。

本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode】104. 二叉树的最大深度(简单)——代码随想录算法训练营Day16

题目链接&#xff1a;104. 二叉树的最大深度 题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例…

Docker网络配置与自定义IP容器通信

目录 前言 一、docker网络配置 1. bridge 虚拟网桥 2. host 网络模式 3. none 网络模式 4. 自定义container网络模式 二、自定义IP容器通信 1. 自定义IP 2. 创建所需容器&#xff08;mysql&#xff0c;tomcat&#xff09; 3. 准备项目资源 4. 构建Nginx实现负载均衡…

垃圾回收小程序:环保与便捷的完美结合

一、引言 随着科技的发展&#xff0c;移动应用程序已经成为人们日常生活中不可或缺的一部分。其中&#xff0c;废品回收小程序以其独特的价值和功能&#xff0c;日益受到人们的关注和青睐。本文将探讨废品回收小程序开发的重要性、功能特点、技术实现和未来发展趋势。 二、废…

AOP切面

什么是Spring的AOP AOP在spring中又叫“面向切面编程”&#xff0c;它可以说是对传统我们面向对象编程的一个补充&#xff0c;从字面上顾名思义就可以知道&#xff0c;它的主要操作对象就是“切面”&#xff0c;所以我们就可以简单的理解它是贯穿于方法之中&#xff0c;在方法…

springboot家乡特色推荐系统源码和论文

在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括家乡特色推荐的网络应用&#xff0c;在外国家乡特色推荐系统已经是很普遍的方式&#xff0c;不过国内的管理网站可能还处于起步阶段。家乡特色推荐系统采用java技术&#xff0…

E3 基于Mysql的SQL应用和存储过程

一、实验目的: Mysql平台要求你熟练使用MySQL基本指令&#xff0c;完成对程序的控制与管理&#xff0c;并根据要求写存储过程。 二、实验要求: 1、基本硬件配置:英特尔Pentium III 以上,大于4G内存&#xff1b; 2、软件要求:Mysql&#xff1b; 3、时间:1小时&#xff1b; …

ggplot2 -- x轴相关操作

文章目录 刻度标签倾斜替换x轴刻度标签改变X刻度标签大小及颜色 演示数据集 library(ggplot2)# 示例数据 data <- data.frame(x 1:5,y c(3, 5, 2, 7, 4) ) data # x y #1 1 3 #2 2 5 #3 3 2 #4 4 7 #5 5 4刻度标签倾斜 p1 <- ggplot(data, aes(x x, y y)) geom_bar…

产品解读 | 新一代湖仓集存储,多模型统一架构,高效挖掘数据价值

星环科技TDH一直致力于给用户带来高性能、高可靠的一站式大数据基础平台&#xff0c;满足对海量数据的存储和复杂业务的处理需求。 同时在易用性方面持续深耕&#xff0c;降低用户开发和运维成本&#xff0c;让数据处理平民化&#xff0c;助力用户以更便捷、高效的方式去挖掘数…

【Kafka】Kafka安装:Linux本地和Docker

目录 Linux本地安装kafkajava环境配置Zookeeper的安装配置Kafka的安装与配置生产与消费 Docker安装kafkaZookeeper安装Kafka安装 Linux本地安装kafka java环境配置 1、上传jdk-8u261-linux-x64.rpm到服务器并安装&#xff1a; rpm -ivh jdk-8u261-linux-x64.rpm2、配置环境变…

WorkPlus移动应用管理平台,助力企业实现高效移动办公

在移动办公成为当今工作方式的主流趋势下&#xff0c;管理和运营企业移动应用成为了提高工作效率和数据安全的重要环节。而移动应用管理平台作为实现移动办公高效管理的关键工具&#xff0c;WorkPlus以其领先的性能和全面的功能&#xff0c;助力企业实现高效移动办公。 为何选…

DP读书:在常工院的2023年度总结

DarrenPig的年度总结 这是最好的时代&#xff0c;这是最坏的时代。——狄更斯 这是最好的时代&#xff0c;这是最坏的时代。——狄更斯 这是最好的时代&#xff0c;这是最坏的时代。——狄更斯 一、2023我的感受 不就是2023吗&#xff0c;不就是一年的经历吗&#xff0c;大家…

如何使用Docker部署导航页工具Dashy并实现任意浏览器远程访问——“cpolar内网穿透”

文章目录 简介1. 安装Dashy2. 安装cpolar3.配置公网访问地址4. 固定域名访问 简介 Dashy 是一个开源的自托管的导航页配置服务&#xff0c;具有易于使用的可视化编辑器、状态检查、小工具和主题等功能。你可以将自己常用的一些网站聚合起来放在一起&#xff0c;形成自己的导航…

小红书商品笔记发布流程,如何避免盘营销

随着平台营销内容不断被管制&#xff0c;商品笔记慢慢出现在了人们的视野&#xff0c;这同时也意味着达人和品牌方们&#xff0c;可以名正言顺的在笔记内容中植入产品。商品链接的开通意味着&#xff0c;不管是达人还是品牌转化率都会进一步提升&#xff0c;今天来马文化传媒和…

AIGC:让生成式AI成为自己的外脑(文末送书)

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 什么是AIGC?二. AIGC如何运作&#xff1f;2.1 步骤一&#xff1a;收集数据2.…

仅使用 Python 创建的 Web 应用程序(前端版本)第06章_登录页面

从本章开始,我们将创建每个页面。 本栏的例子 可以访问这里, WTS 首先是登录页面。 完成后的图像如下 创建过程如下 No类型内容1Model创建继承BaseDataModel的数据类User、Session2MockDB创建用户表并添加管理员/成员用户3Service创建AuthAPIClient、UserAPIClient4Page定义…

利用Burp Suite观察https通联

对使用 HTTPS 协议的应用程序进行测试时&#xff0c;常使用 bp 观察流量&#xff0c;为能成功建立HTTPS联接&#xff0c;在将bp设置居代理的同时&#xff0c;还必须导入bp伪证书&#xff0c;这样才能修改请求和响应&#xff0c;加密和解密流量&#xff0c;成功模拟浏览的各种动…

Maven构建工具:Java项目的不可或缺之选

引言 在Java开发领域&#xff0c;构建工具是项目中至关重要的一环。Maven&#xff08;Maven Apache&#xff09;是一个强大的构建工具&#xff0c;用于管理项目的构建、依赖和文档等方面。本篇博文将介绍如何配置和使用Maven来构建和管理Java项目。 第一部分&#xff1a;Mave…

数据脱敏(三)脱敏算法-遮盖算法

脱敏算法篇使用阿里云数据脱敏算法为模板,使用算子平台快速搭建流程来展示数据 遮盖脱敏是一种数据脱敏技术&#xff0c;它的主要目的是通过隐藏或替换敏感信息来保护数据安全&#xff0c;同时保持数据的其他特性不变&#xff0c;以便于数据的进一步使用和分析。这种脱敏技术适…

九州金榜|过年期间如何合理规划孩子学习?

随着春节的临近&#xff0c;家家户户都沉浸在喜庆的氛围中。对于孩子们来说&#xff0c;过年意味着热闹、欢笑和丰盛的美食。然而&#xff0c;即使是过年&#xff0c;学习也不应被忽视。九州金榜家庭教育将和大家一起探讨如何合理安排过年期间孩子的学习。 一、保持学习持续性 …

探索编程世界的利器!选择哪个IDE,成就新手开发之路?

文章目录 一、IDE的概念和作用IDE是什么&#xff1f;为什么说选择一款IDE对开发者来说可以起到事半功倍的作用&#xff1f; 二、当下备受推崇的IDE有哪些&#xff1f;1. Visual Studio Code2. PyCharm3. IntelliJ IDEA 三、如何选择一个适合自己的IDE&#xff1f;四、IDE的使用…