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、中间件、架构设计、面试题、程序员攻略等

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

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

相关文章

Ceph篇之利用Prometheus监控ceph服务

一、Ceph内置模块 Ceph manager 内部的模块中包含了 prometheus 的监控模块,并监听在每个 manager 节点的 9283 端口&#xff0c;该端口用于将采集到的信息通过 http 接口向 prometheus 提供数据。 二、监控搭建 1、启用 prometheus 监控模块 ceph mgr module enable promethe…

Java+Spring Cloud +Vue+UniApp微服务智慧工地云平台源码

目录 智慧工地云平台功能 【劳务工种】所属工种有哪些&#xff1f; 1.管理人员 2.信息采集 3.证件管理 4.考勤管理 5.考勤明细 6.工资管理 7.现场统计 8.WIFI教育 9.课程库管理 10.工种管理 11.分包商管理 12.班组管理 13.项目管理 智慧工地管理平台是以物联网、…

Windows物理主机迁移至VMware ESXI服务器

文章目录 物理主机迁移至VMware ESXI服务器一、迁移环境二、迁移步骤&#xff11;、主机A准备工作&#xff11;、关闭主机A的防火墙&#xff12;、主机A需要设置管理员密码&#xff13;、主机A需要设置允许共享访问 &#xff12;、主机B操作步骤1、安装**VMware vCenter Conver…

鸿蒙开发第2篇__装饰器

在ArkTS中&#xff0c; 有装饰器语法。 装饰器用于装饰类、结构、方法、变量&#xff0c;赋予其特殊的含义。 1. Component 表示自定义组件&#xff0c; 用此装饰器的组件是可重用的&#xff0c;可以与其他组件重合 此装饰器装饰的 struct 表示该结构体具有组件化能力&#…

Java复习系列之阶段二:数据库(2)

1. 基础语法 1.1 DQL&#xff08;数据查询语句&#xff09; 执行顺序&#xff1a; from、join 、on、where、group by、having、select、distinct、order by、limit 1.2 DML&#xff08;数据修改语言&#xff09; 对数据表的增删改 insert into update set delete form 1.…

新年短信群发选择自己发还是106短信平台合适?

选择自己发新年短信群发还是106短信平台&#xff0c;取决于您的需求和资源。 如果您需要发送的短信数量较少&#xff0c;或者您有特定的发送需求&#xff0c;例如需要发送一些包含敏感信息的短信&#xff0c;那么您可以选择自己发送。您可以使用手机或电脑上的短信应用程序来发…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 1月26日,星期五

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年1月26日 星期五 农历腊月十六 1、 教育部&#xff1a;保障符合就业条件的公费师范生有编有岗&#xff0c;全部落实任教学校。 2、 禁用有毒有害材料&#xff01;我国首部快递包装强制性国家标准6月1日实施。 3、 中疾控&…

BabylonJS 6.0文档 Deep Dive 摄像机(六):遮罩层和多相机纹理

1. 使用遮罩层来处理多个摄影机和多网格物体 LayerMask是分配给每个网格&#xff08;Mesh&#xff09;和摄像机&#xff08;Camera&#xff09;的一个数。它用于位&#xff08;bit&#xff09;级别用来指示灯光和摄影机是否应照射或显示网格物体。默认值为0x0FFFFFFF&#xff…

JOSEF约瑟 过电流继电器 JL15-1200/11 一开一闭 吊车起重机交直流可用

系列型号 JL15-/11: JL15-1.5/11电流继电器JL15-2.5/11电流继电器 JL15-5/11电流继电器JL15-10/11电流继电器 JL15-15/11电流继电器JL15-20/11电流继电器 JL15-30/11电流继电器JL15-40/11电流继电器 JL15-60/11电流继电器JL15-80/11电流继电器 JL15-100/11电流继电器JL1…

code server安装使用教程

1. 安装 1.1. 下载code-server安装包 类似这种文件&#xff1a;code-server-3.10.2-linux-amd64.tar.gz 解压&#xff1a;tar -xvf code-server-3.10.2-linux-amd64.tar.gz 1.2 &#xff08;可选&#xff09;建立软连接 ln -s path/to/code-server-3.10.2-linux-amd64/bin…

音频前置放大器电路图大全

音频前置放大器电路图&#xff08;一&#xff09; 在本设计中&#xff0c;前置放大器的增益控制采用直流音量控制方式&#xff0c;其具体实现如图1所示。前置放大器是由全差分运放和电阻构成的反相比例放大器&#xff0c;其增益由反馈电阻与输人电阻的比值决定。外部输人的直流…

铅酸电池废液处理需要哪些工艺设备

铅酸电池废液处理是一项非常重要的环保任务。为了保护环境和人类的健康&#xff0c;我们需要采取一系列工艺设备来处理这些废液。那么&#xff0c;到底需要哪些设备呢&#xff1f;让我们深入探讨一下吧。 首先&#xff0c;废液处理的第一步是预处理阶段。在这个阶段&#xff0c…

华为数通方向HCIP-DataCom H12-831题库(判断题:101-120)

第101题 路由协议通过Hello报文就可以检测到故障,所以不需要BFD 正确 错误 答案: 错误 解析: Hello机制是可以检测到网络故障,但是效率太低,平常会结合BFD来快速检测故障机制,能够实现快速故障检测。 第102题 VXLAN采用Mac in TCP封装方式将二层报文用三层协议进行封装 …

【CANoe使用大全】——Trace窗口

文章目录 1.Trace作用2.Trace窗口打开方式2.1.Analysis—>Trace2.2.Measurement Setup ------> Trace 3.Trace窗口菜单栏介绍3.1. Detail View3.1. Statistic View3.3.Difference view3.4.Predefined filter3.5.Analysis filter3.6.其他过滤方式 4. 其他窗口介绍5. 报文保…

阿里巴巴Java开发手册(详尽版)

点击下载 阿里巴巴Java开发手册

【RabbitMQ】延迟队列之死信交换机

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《RabbitMQ实战》。&#x1f3af;&#x1f3af; &am…

前端学习生产环境、开发环境、测试环境

1、路径 定义是什么环境 NODE_ENVdevelopment 开发环境 2、.env 端口号 3、.env.development 开发环境 4、.env.production 生产环境 5、.env.test 测试环境 6、如何访问&#xff0c;通过process.env进行访问 学习中.......

简化java代码:mapstruct + 策略模式

目录 目的 准备 注意 相同类型-属性名不同 实体类 映射 使用 验证-查看实现类 测试 不同类型(策略模式) 实体类 映射 工具类 使用&#xff1a;对象拷贝 验证-查看实现类 测试 使用&#xff1a;集合拷贝 测试 策略模式说明 准备-依赖 目的 简化 BeanUtils.…

Axios取消请求:AbortController

AbortController AbortController() 构造函数创建了一个新的 AbortController 实例。MDN官网给出了一个利用AbortController取消下载视频的例子。 核心逻辑是&#xff1a;利用AbortController接口的只读属性signal标记fetch请求&#xff1b;然后在需要取消请求的时候&#xff0…

python算法与数据结构---单调栈与实践

单调栈 单调栈是一个栈&#xff0c;里面的元素的大小按照它们所在栈的位置&#xff0c;满足一定的单调性&#xff1b; 性质&#xff1a; 单调递减栈能找到左边第一个比当前元素大的元素&#xff1b;单调递增栈能找到左边第一个比当前元素小的元素&#xff1b; 应用场景 一般用…