SpringBoot - 用责任链模式实现业务编排

文章目录

  • 前因
  • 责任链:像工作台一样组织代码
  • Code
    • SEQ
    • 3.1 定义处理器规范
    • 3.2 实现具体处理器
    • 3.3 共享上下文
    • 3.4 组装责任链
  • 适用场景
  • 优势

在这里插入图片描述


前因

2000多行的业务逻辑里,各种校验规则、促销计算、库存操作像意大利面条一样缠绕在一起。最要命的是这样的代码结构:

public void createOrder(OrderRequest request) {// 参数校验if(request.getUserId() == null){throw new Exception("用户ID不能为空");}if(request.getItems().isEmpty()){throw new Exception("商品不能为空");}// 库存检查for(Item item : request.getItems()){if(!inventoryService.checkStock(item)){throw new Exception("库存不足");}}// 优惠计算if(request.getCouponId() != null){// 复杂的优惠计算逻辑...}// 风控检查if(riskService.checkRisk(request)){throw new Exception("风控拦截");}// 保存订单// ...
}

每次需求变更都像在雷区跳舞,稍有不慎就会引发连锁反应。新来的小伙伴看着满屏的if-else,战战兢兢地问:“咱们能不能换个写法?”


责任链:像工作台一样组织代码

这时候就该责任链模式登场了!这个设计模式的核心思想是:把每个处理步骤拆成独立的处理器,像流水线一样连接起来

想象一下快递分拣系统:

  1. 包裹先过安检机(校验处理器)
  2. 然后到分拣区(路由处理器)
  3. 接着称重计费(价格处理器)
  4. 最后装车发货(持久化处理器)

每个环节只关心自己的职责,处理完就交给下一个环节。这样无论是要调整环节顺序,还是增加新的处理环节,都变得非常灵活。

Code

SEQ

客户端 OrderController OrderChainManager ValidationHandler StockCheckHandler DiscountHandler OrderContext POST /orders (OrderRequest) 创建OrderContext executeChain(context) 获取所有处理器并排序 handle(context) 参数校验 return false return true alt [校验失败] [校验成功] handle(context) 库存检查 return false return true alt [库存不足] [库存充足] handle(context) 优惠计算 return true 继续后续处理器... alt [库存充足] alt [校验成功] 返回OrderResult 200 OK (OrderResult) 400 BadRequest (ErrorInfo) alt [全部成功] [任何失败] 客户端 OrderController OrderChainManager ValidationHandler StockCheckHandler DiscountHandler OrderContext

3.1 定义处理器规范

package com.artisan.chain.handler;import com.artisan.chain.model.OrderContext;/*** 处理器接口定义*/
public interface OrderHandler {/*** 处理 : 返回true ,继续处理,返回false,终止处理** @param orderContext* @return*/boolean handle(OrderContext orderContext);/*** 获取处理顺序** @return*/int getOrder();
}

3.2 实现具体处理器

参数校验处理器

 package com.artisan.chain.handler;import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderRequest;
import com.artisan.chain.model.OrderResult;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;@Component
@Order(100)
public class ValidationHandler implements OrderHandler {/*** 处理订单请求的函数* 该函数主要用于验证订单请求中的必要信息是否完整* 如果验证失败,它会设置上下文结果并返回false** @param context 订单上下文,包含订单请求和结果* @return 如果验证通过,返回true;否则返回false*/@Overridepublic boolean handle(OrderContext context) {// 获取订单请求OrderRequest request = context.getRequest();// 验证用户ID是否为空if (StringUtils.isEmpty(request.getUserId())) {// 如果用户ID为空,设置验证失败的结果并返回context.setResult(OrderResult.fail("VALIDATION_ERROR", "用户ID不能为空"));return false;}// 验证订单商品列表是否为空if (CollectionUtils.isEmpty(request.getItems())) {// 如果订单商品列表为空,设置验证失败的结果并返回context.setResult(OrderResult.fail("VALIDATION_ERROR", "订单商品不能为空"));return false;}// 如果所有验证都通过,返回truereturn true;}/*** 获取当前对象的顺序值** 顺序值用于确定对象处理的优先级或显示顺序* 值越小 优先级越高** @return 返回顺序值100,表示当前对象的默认顺序位置*/@Overridepublic int getOrder() {return 100;}
}

库存检查处理器

 package com.artisan.chain.handler;import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderRequest;
import com.artisan.chain.model.OrderResult;
import com.artisan.chain.service.InventoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** 库存检查处理器,用于在订单处理流程中检查商品库存*/
@Component
@Order(200)
public class StockCheckHandler implements OrderHandler{// 注入库存服务,用于检查商品库存情况@Autowiredprivate InventoryService inventoryService;/*** 处理订单中的库存检查逻辑* 遍历订单中的每项商品,检查库存是否充足* 如果任何商品的库存不足,订单处理失败,并更新订单上下文的结果** @param context 订单上下文,包含订单请求和处理结果* @return 如果所有商品库存充足,返回true;否则返回false*/@Overridepublic boolean handle(OrderContext context) {// 遍历订单中的每个商品项,检查库存for (OrderRequest.OrderItem item : context.getRequest().getItems()) {// 如果库存检查失败,更新订单上下文的结果为库存错误,并返回falseif (!inventoryService.checkStock(item.getSkuId(), item.getQuantity())) {context.setResult(OrderResult.fail("STOCK_ERROR","商品[" + item.getSkuId() + "]库存不足"));return false;}}// 所有商品库存充足,返回truereturn true;}/*** 获取订单处理的顺序* 用于确定在订单处理流程中执行库存检查的顺序** @return 订单处理的顺序值*/@Overridepublic int getOrder() {return 200;}
}
package com.artisan.chain.handler;import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderRequest;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.util.List;/*** 定义一个处理折扣的组件*/
@Component
@Order(300)
public class DiscountHandler implements OrderHandler {/*** 处理订单中的折扣计算** @param context 订单上下文,包含订单请求和属性* @return 总是返回true,表示处理成功*/@Overridepublic boolean handle(OrderContext context) {// 模拟优惠计算double total = calculateTotal(context.getRequest().getItems());double discount = calculateDiscount(total, context.getRequest().getCouponId());// 将最终金额存入上下文中context.getAttributes().put("finalAmount", total - discount);return true;}/*** 计算订单总金额** @param items 订单中的商品列表* @return 订单总金额*/private double calculateTotal(List<OrderRequest.OrderItem> items) {// 模拟价格计算,这里简化处理,实际应根据商品价格和数量计算return items.stream().mapToDouble(item -> item.getQuantity() * 100.0) // 模拟价格.sum();}/*** 计算折扣金额** @param total    订单总金额* @param couponId 优惠券ID,如果为空则不应用折扣* @return 折扣金额*/private double calculateDiscount(double total, String couponId) {// 模拟优惠计算,如果有优惠券ID,则应用10%的折扣return couponId != null ? total * 0.1 : 0;}/*** 获取处理顺序** @return 处理顺序值*/@Overridepublic int getOrder() {return 300;}
}

3.3 共享上下文

package com.artisan.chain.model;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.HashMap;
import java.util.Map;/*** 上下文对象(共享数据载体)** 订单上下文类,用于处理订单请求并生成订单结果* 它封装了订单请求、处理结果以及相关属性**/@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderContext {/*** 订单请求对象,包含订单的相关请求信息*/private OrderRequest request;/*** 订单处理结果对象,用于存储订单处理后的信息*/private OrderResult result = new OrderResult();/*** 订单相关属性集合,用于存储订单处理过程中需要的临时信息* 键为属性名称,值为属性值*/private Map<String, Object> attributes = new HashMap<>();/*** 构造方法,初始化订单上下文** @param request 订单请求对象,不能为空*/public OrderContext(OrderRequest request) {this.request = request;}
}

3.4 组装责任链

 package com.artisan.chain.manager;import com.artisan.chain.handler.OrderHandler;
import com.artisan.chain.model.OrderContext;
import com.artisan.chain.model.OrderResult;
import org.springframework.stereotype.Component;import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;/*** 订单链式处理器管理类* 通过链式调用多个OrderHandler来处理订单逻辑* 该类负责构建和管理这些处理订单的处理器链*/
@Component
public  class OrderChainManager {/*** 存储所有的订单处理器,按照处理顺序排序*/private final List<OrderHandler> handlers;/*** 构造函数,初始化订单处理器链* @param handlers 一个未排序的订单处理器集合*/public OrderChainManager(List<OrderHandler> handlers) {// 根据每个处理器的顺序值进行排序,确保它们按照正确的顺序执行this.handlers = handlers.stream().sorted(Comparator.comparingInt(OrderHandler::getOrder)).collect(Collectors.toList());}/*** 执行订单处理逻辑* 遍历每个订单处理器,直到所有处理器都处理完毕或某个处理器决定中断链式处理* @param context 订单上下文,包含订单的处理信息和结果* @return 处理后的订单结果*/public OrderResult execute(OrderContext context) {// 遍历处理器链,如果某个处理器处理失败(返回false),则中断链式处理for (OrderHandler handler : handlers) {if (!handler.handle(context)) {break;}}// 返回最终的订单处理结果return context.getResult();}}
package com.artisan.chain.config;import com.artisan.chain.handler.OrderHandler;
import com.artisan.chain.manager.OrderChainManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.Collections;
import java.util.List;/*** 配置类,用于定义和配置订单处理链相关的Bean*/
@Configuration
public class HandlerConfig {/*** 创建并配置OrderChainManager Bean** @param handlersProvider 一个对象提供者,用于提供订单处理器列表如果未找到则提供一个空列表* @return 返回一个OrderChainManager实例,用于管理订单处理链*/@Beanpublic OrderChainManager orderChainManager(ObjectProvider<List<OrderHandler>> handlersProvider) {// 初始化OrderChainManager,使用提供的订单处理器列表,如果没有提供则使用空列表return new OrderChainManager(handlersProvider.getIfAvailable(Collections::emptyList));}
}
# 测试成功案例
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{"userId": "user123","items": [{"skuId": "SKU001", "quantity": 2},{"skuId": "SKU002", "quantity": 1}],"couponId": "COUPON2023"
}'# 测试库存不足
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{"userId": "user123","items": [{"skuId": "SKU001", "quantity": 200}]
}'

适用场景

  • 多步骤流程:订单创建、审批流、支付流程等

  • 动态业务:需要频繁调整步骤顺序的业务

  • 复杂校验:多层次、多条件的校验场景

  • 插件式架构:需要动态加载/卸载功能的系统

优势

  • 解耦性:每个处理逻辑独立成Handler,修改单个处理器不影响其他组件
  • 可扩展性:新增业务逻辑只需添加新Handler,无需修改主流程
  • 动态编排:通过配置灵活调整处理器执行顺序和启用状态
  • 可测试性:每个Handler可单独进行单元测试
  • 复用性:通用处理器(如日志记录)可跨多个业务场景复用

在这里插入图片描述

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

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

相关文章

upload-labs详解(13-20)文件上传分析

目录 upload-labs-env upload-labs-env第十三关 文件包含漏洞 代码 测试 上传一个.jpg图片 上传一个.png文件 上传一个.gif图片 upload-labs-env第十四关 代码 思路 upload-labs-env第十五关 代码 思路 upload-labs-env第十六关 代码 思路 测试 上传gif格式…

网络安全通信架构图

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 在安全通信里面我经常听到的2个东西就是SSL和TLS&#xff0c;这2个有什么区别呢&#xff1f;以及HTTPS是怎么通信的&#xff1f;包括对称加密、非对称加密、摘要、…

Java中的String类

目录 1. String类的重要性 2. 常用方法 2.1 字符串构造 2.2 String对象的比较 2.3 字符串查找 2.4 转化 2.5 字符串替换 2.6 字符串拆分 2.7 字符串截取 2.8 其他操作方法 2.9 字符串的不可变性 2.10 字符串修改 3. StringBuilder和StringBuffer 3.1 StringBuilde…

深度分页介绍及优化建议

深度分页介绍 查询偏移量过大的场景我们称为深度分页&#xff0c;这会导致查询性能较低&#xff0c;例如&#xff1a; # MySQL 在无法利用索引的情况下跳过1000000条记录后&#xff0c;再获取10条记录 SELECT * FROM t_order ORDER BY id LIMIT 1000000, 10 深度分页问题的原…

live555推流服务器异常

1.后端异常信息&#xff1a; MultiFramedRTPSink::afterGettingFrame1(): The input frame data was too large for our buffer size (100176). 48899 bytes of trailing data was dropped! Correct this by increasing "OutPacketBuffer::maxSize" to at least m…

每日OJ_牛客_宵暗的妖怪_DP_C++_Java

目录 牛客_宵暗的妖怪_DP 题目解析 C代码 Java代码 牛客_宵暗的妖怪_DP 宵暗的妖怪 描述&#xff1a; 露米娅作为宵暗的妖怪&#xff0c;非常喜欢吞噬黑暗。这天&#xff0c;她来到了一条路上&#xff0c;准备吞噬这条路上的黑暗。这条道路一共被分为n 部分&…

20250306-笔记-精读class CVRPEnv:step(self, selected)

文章目录 前言一、if self.time_step<4:控制时间步的递增判断是否在配送中心特定时间步的操作更新更新当前节点和已选择节点列表更新需求和负载更新访问标记更新负无穷掩码更新步骤状态&#xff0c;将更新后的状态同步到 self.step_state 二、使用步骤总结 前言 class CVRP…

Flowable 基本入门

flowable.7z官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘 1、Flowable介绍 Flowable是BPMN的一个基于java的软件实现&#xff0c;不过Flowable不仅仅包括BPMN&#xff0c;还有DMN决策表和CMMN Case管理引擎&#xff0c;并且有自己的用户管理、微服务API等一系列功能&a…

完全背包-一维数组

52. 携带研究材料&#xff08;第七期模拟笔试&#xff09; 题目描述 小明是一位科学家&#xff0c;他需要参加一场重要的国际科学大会&#xff0c;以展示自己的最新研究成果。他需要带一些研究材料&#xff0c;但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和…

景联文科技:以专业标注赋能AI未来,驱动智能时代的精准跃迁

在人工智能技术重塑全球产业格局的今天&#xff0c;高质量训练数据已成为驱动算法进化的核心燃料。作为数据智能服务领域的领军者&#xff0c;景联文科技深耕数据标注行业多年&#xff0c;以全栈式数据解决方案为核心&#xff0c;构建起覆盖数据采集、清洗、标注、质检及算法调…

洛谷B2074 计算星期几

B2074 计算星期几 - 洛谷 代码区&#xff1a; #include<algorithm> #include<iostream> #include<unordered_map> #include<string> using namespace std; int main() {unordered_map<int, string> m { { 1,"Monday" },{2,"Tue…

协同过滤推荐算法+微信小程序的农产品团购推荐平台(程序+论文+讲解+安装+调试+售后)

感兴趣的可以先收藏起来&#xff0c;还有大家在毕设选题&#xff0c;项目以及论文编写等相关问题都可以给我留言咨询&#xff0c;我会一一回复&#xff0c;希望帮助更多的人。 系统介绍 在当今时代&#xff0c;科学技术正以令人瞩目的速度迅猛进步&#xff0c;经济社会也随之…

十大经典排序算法简介

一 概述 本文对十大经典排序算法做简要的总结(按常用分类方式排列),包含核心思想、时间/空间复杂度及特点。 二、比较类排序 1. 冒泡排序 (BUBBLE SORT) 思想:重复交换相邻逆序元素,像气泡上浮 复杂度: 时间:O(n^2)(最好情况O(n)) 空间:O(1) 特点:简单但效率低,稳…

[自然语言处理]pytorch概述--什么是张量(Tensor)和基本操作

pytorch概述 PyTorch 是⼀个开源的深度学习框架&#xff0c;由 Facebook 的⼈⼯智能研究团队开发和维护&#xff0c;于2017年在GitHub上开源&#xff0c;在学术界和⼯业界都得到了⼴泛应⽤ pytorch能做什么 GPU加速自动求导常用网络层 pytorch基础 量的概念 标量&#xf…

Spring统一格式返回

目录 一&#xff1a;统一结果返回 1&#xff1a;统一结果返回写法 2&#xff1a;String类型报错问题 解决方法 二&#xff1a;统一异常返回 统一异常返回写法 三&#xff1a;总结 同志们&#xff0c;今天咱来讲一讲统一格式返回啊&#xff0c;也是好久没有讲过统一格式返…

【无标题】四色拓扑模型与宇宙历史重构的猜想框架

### 四色拓扑模型与宇宙历史重构的猜想框架 --- #### **一、理论基础&#xff1a;四色拓扑与时空全息原理的融合** 1. **宇宙背景信息的拓扑编码** - **大尺度结构网络**&#xff1a;将星系团映射为四色顶点&#xff0c;纤维状暗物质结构作为边&#xff0c;构建宇宙尺度…

蓝桥杯 封闭图形个数

蓝桥杯 封闭图形个数 题目 链接 解答 # 数字个数 n int(input()) # 数字 ls input().split() # 统计数字的圈数 o_nums {} for i, x in enumerate(ls):o_num 0for c in x:if int(c) in [0, 4, 6, 9]:o_num 1elif c 8:o_num 2o_nums[i] o_num # 字典根据圆圈数排序 …

基于javaweb的SpringBoot学生在线考试管理系统设计和实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

国产编辑器EverEdit - 超多样式设置

1 设置-编辑-样式 1.1 设置说明 1.1.1 折叠样式 默认为箭头&#xff0c;折叠样式选项如下&#xff1a; 箭头&#xff1a; 矩形和线条 五边形 圆形图标 1.1.2 光标样式 光标用于指示当前用户输入位置&#xff0c;光标样式选项如下&#xff1a; 默认 纤细 字宽 …

Linux - 线程控制

一、线程概念 1&#xff09;线程地址空间 线程与进程共享相同的虚拟地址空间&#xff0c;因此线程在访问内存时与进程没有本质的区别。但线程共享和独占的内存区域有不同的特点&#xff0c;理解这些特性对于正确使用线程至关重要。 1. 线程地址空间的组成 线程的地址空间是…