⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 风象南 「风象南」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

不知道你有没有遇到过这种情况:一个订单创建的接口,刚上线的时候只有几十行代码,逻辑也很简单。但随着业务迭代,三个月后变成了这样:

public void createOrder(OrderRequest request) {
// 参数校验
if (request.getUserId() == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
// ... 省略 20 行校验代码

// 权限校验
if (!checkPermission(request.getUserId())) {
throw new ForbiddenException("无权限");
}

// 业务校验
if (getOrderCount(request.getUserId()) >= 10) {
throw new BusinessException("订单数量超限");
}
// ... 省略 15 行业务校验

// 创建订单
Order order = buildOrder(request);
orderMapper.insert(order);

// 写操作日志
try {
operateLogService.log(order.getId(), "CREATE");
} catch (Exception e) {
// 日志失败不影响主流程
log.error("写日志失败", e);
}

// 发送通知
try {
notificationService.send(order);
} catch (Exception e) {
log.error("发送通知失败", e);
}

// 异步风控检查
asyncRiskCheckService.check(order);
}

几百行代码,if、try-catch 到处都是。更可怕的是:

  • 日志写失败要不要抛异常?
  • 通知发送失败要不要回滚订单?
  • 新增一个”优惠券核销”逻辑加在哪里?
  • 测试环境想关闭风控检查怎么搞?

问题出在哪?

仔细看看上面的代码,其实它想做的事情可以拆成这么几个步骤:

1. 校验参数
2. 校验权限
3. 校验业务规则
4. 创建订单(核心)
5. 记录日志
6. 发送通知
7. 风控检查

这些步骤有几个特点:

有的必须成功:比如参数校验、订单创建
有的可以失败:比如日志记录、通知发送
有的同步执行:比如各种校验
有的异步执行:比如风控检查
有的可能要关闭:比如测试环境不需要风控

但现在的写法,所有这些逻辑都混在一起,顺序、失败策略、是否可选,全靠代码里的 if 和 try-catch 来控制,能不乱吗?

有没有更好的写法?

如果把这些步骤抽象成一个个”节点”,然后用一个”管道”把它们串起来,会怎样?

public interface PipelineNode<T> {
void execute(PipelineContext<T> context);
}

每个节点只做一件事,不关心前后是谁,不关心自己是第几个执行。

然后定义一个管道来执行这些节点:

public class ExecutionPipeline<T> {

private final List<PipelineNode<T>> nodes;

public void execute(T data) {
PipelineContext<T> ctx = new PipelineContext<>(data);
for (PipelineNode<T> node : nodes) {
if (ctx.isInterrupted()) {
break;
}
node.execute(ctx);
}
}
}

现在再看看订单创建怎么写:

Pipeline pipeline = Pipeline.builder()
.add(new ParamValidateNode()) // 参数校验
.add(new PermissionCheckNode()) // 权限校验
.add(new BusinessValidateNode()) // 业务校验
.add(new CreateOrderNode()) // 创建订单
.add(new OperateLogNode()) // 写日志
.add(new NotificationNode()) // 发通知
.add(new AsyncRiskCheckNode()) // 风控检查
.build();

pipeline.execute(orderRequest);

主流程只剩下 10 行代码,每个节点的逻辑独立封装。

这样做有什么好处?

1. 节点可插拔

想加一个”优惠券核销”逻辑?加个节点就行了:

.add(new CouponDeductNode())

想关闭某个功能?去掉对应节点,或者通过配置控制:

.add(env.isProd() ? new RiskCheckNode() : new SkipNode())

2. 失败策略清晰

在 PipelineContext 里定义好失败策略:

public enum FailureStrategy {
CONTINUE, // 失败后继续执行
STOP, // 失败后中断管道
ROLLBACK // 失败后回滚(需要配合事务)
}

日志节点的失败策略是 CONTINUE,订单创建节点的失败策略是 STOP,一目了然。

3. 节点可复用

参数校验节点、权限校验节点、日志节点,都可以在其他业务里复用。

4. 执行顺序可控

节点的执行顺序由管道定义,不用在每个节点里写代码控制。

执行管道:一种工程化方案

上面介绍的方案既执行管道(Execution Pipeline)

它不是某个框架,而是一种工程设计模式:把一次业务执行拆成多个可插拔、有顺序、可控制的执行阶段,用统一机制串起来。

它跟 AOP、责任链有什么区别?

你可能想到了 AOP、责任链、工作流引擎这些概念,它们确实有相似之处:

方案 适用场景 局限性
AOP 通用的横切逻辑(日志、事务) 顺序不直观,难以做流程控制
Filter/Interceptor HTTP 请求级别的处理 只能用于请求处理
@Async 异步执行 只解决并发,不解决流程编排
责任链 动态组合处理逻辑 缺少工程化封装
工作流引擎 复杂的业务流程 太重,单机应用用不上

执行管道可以说是工程化的责任链模式,专门用于解决单机应用内的业务流程编排问题。

什么时候用?

不是所有场景都需要执行管道。如果你的代码:

  • 步骤固定、逻辑简单,直接写就行
  • 流程复杂但不会变化,也没必要过度设计

但当出现这些信号时,可以考虑:

  • 一个方法过长,不利于阅读维护
  • 有多个”可选”或”异步”的执行步骤
  • 经常需要插入新的执行逻辑
  • 不同环境需要不同的执行流程

执行管道特别适合单机应用内的复杂业务流程,比如订单创建、用户注册、数据导入等场景。

写在最后

好的代码不是一次写成的,而是在不断重构中进化出来的。

如果你的项目里已经有了那种”巨无霸”方法,不用急着全部推倒重来。先试着把其中一个独立的逻辑抽成节点,然后逐步迁移,代码会越来越清晰。

https://github.com/yuboon/java-examples/tree/master/springboot-pipeline
文章目录
  1. 1. 问题出在哪?
  2. 2. 有没有更好的写法?
  3. 3. 这样做有什么好处?
    1. 3.0.1. 1. 节点可插拔
    2. 3.0.2. 2. 失败策略清晰
    3. 3.0.3. 3. 节点可复用
    4. 3.0.4. 4. 执行顺序可控
  • 4. 执行管道:一种工程化方案
  • 5. 它跟 AOP、责任链有什么区别?
  • 6. 什么时候用?
  • 7. 写在最后