责任链模式实战:干掉 200 行 if-else,从 Spring Security 过滤器到企业级订单审核

0 阅读11分钟

扫码_搜索联合传播样式-标准色版.png

一、前言:这坑我替你踩过了

最近在重构公司的订单审核系统,我接手了一段"祖传代码"——

一个 OrderService.process() 方法里堆了 200 多行 if-else

if(风控没过) return;
if(库存不够) return;
if(没支付) return;
if(黑名单) return;
if(超区域) return;
if(优惠券失效) return;
// ... 后面还有 50 个 if

看得我 CPU 温度先上来了。

更离谱的是:产品说要加一个"大促活动专属审核",我盯着这堆 if-else 看了半小时,头发又少了几根。

最后没办法,上 责任链模式。改完之后:

  • 加一个审核环节?新增一个类,链上插一节,完事。
  • 调整顺序?改一行配置
  • 删一个环节?移除一个节点
  • 测试?每个节点单独测,互不干扰。

那一刻我才真正明白:

责任链模式(Chain of Responsibility),不是设计模式里的"选修课",而是企业级开发里的"必修课"。 不会它,你的代码迟早变成意大利面 🍝。


二、什么是责任链模式?

1. 一句话定义

将请求的发送者和接收者解耦,让多个对象都有机会处理这个请求,把这些对象连成一条链,请求沿着链传递,直到有一个对象处理它为止。

2. 生活里的责任链

你以为是新概念?其实你每天都在用。

场景链路
请假审批员工 → 组长 → 经理 → 总监 → CEO
快递分拣北京站 → 华北区 → 北京市 → 朝阳区 → 派送员
击鼓传花鼓声不停,花就一直在传
公司报销500 以下主管批,5000 以下经理批,50000 财务批
Spring 请求CORS → 鉴权 → 日志 → 限流 → Controller

核心本质就一句话:

发送者只管"扔出去",谁接手、怎么处理,发送者不关心

3. 三个核心角色

角色职责人话翻译
抽象处理者(Handler)定义处理接口,持有下一个处理者引用审批模板,规定"你能批多少,批不了交给谁"
具体处理者(ConcreteHandler)实现处理逻辑,能处理就处理,处理不了就往下传组长、经理、总监,每个人各管一段
客户端(Client)拼装责任链,发起请求拼链路的人,把节点串起来

三、纯 vs 不纯责任链

这个点面试经常问,但很多人说不清

1. 纯责任链

一个请求只能被一个处理者处理,处理完就停。

典型场景:请假审批

  • 组长批了 → 后面经理、总监都不会再碰这件事。

2. 不纯责任链

请求可以经过多个处理者,每个都做点事。

典型场景:Spring 过滤器链

  • 日志过滤器 → 鉴权过滤器 → 限流过滤器 → Controller
  • 每个过滤器都"动一下"这个请求,最后所有节点都执行完。

怎么区分?

看你需求是"找一个能处理的人"(纯),还是"让一串人依次处理"(不纯)。


四、Java 代码实现(从入门到企业级)

版本一:青铜版(纯责任链 - 请假审批)

Step 1:请假请求实体

/**
 * 请假请求 - 责任链里传递的"包裹"
 */
@Data
@AllArgsConstructor
public class LeaveRequest {
    /** 请假人 */
    private String name;
    /** 请假天数 */
    private int days;
    /** 请假原因 */
    private String reason;
}

Step 2:抽象处理者

/**
 * 抽象审批者 - 所有审批人的"爸爸"
 */
public abstract class Approver {

    /** 下一个审批人 - 链的核心 */
    protected Approver next;

    /** 当前审批人姓名 */
    protected String name;

    public Approver(String name) {
        this.name = name;
    }

    /**
     * 设置下一个审批人(支持链式调用)
     * 返回 next 是为了能 .setNext(a).setNext(b) 一直点下去
     */
    public Approver setNext(Approver next) {
        this.next = next;
        return next;
    }

    /**
     * 抽象审批方法 - 子类必须实现自己的审批规则
     */
    public abstract void approve(LeaveRequest request);
}

Step 3:具体处理者

/** 组长:批 3 天以内 */
public class TeamLeader extends Approver {
    public TeamLeader() { super("组长"); }

    @Override
    public void approve(LeaveRequest request) {
        if (request.getDays() <= 3) {
            System.out.println("✅ " + name + " 批了 " + request.getName()
                + " 的 " + request.getDays() + " 天假");
        } else if (next != null) {
            // 超出权限?踢给下家
            next.approve(request);
        } else {
            System.out.println("❌ 没人能批,没人能背锅");
        }
    }
}

/** 经理:批 3-7 天 */
public class Manager extends Approver {
    public Manager() { super("经理"); }

    @Override
    public void approve(LeaveRequest request) {
        if (request.getDays() <= 7) {
            System.out.println("✅ " + name + " 批了 " + request.getName()
                + " 的 " + request.getDays() + " 天假");
        } else if (next != null) {
            next.approve(request);
        } else {
            System.out.println("❌ 没人能批");
        }
    }
}

/** 总监:批 7-30 天 */
public class Director extends Approver {
    public Director() { super("总监"); }

    @Override
    public void approve(LeaveRequest request) {
        if (request.getDays() <= 30) {
            System.out.println("✅ " + name + " 批了 " + request.getName()
                + " 的 " + request.getDays() + " 天假");
        } else if (next != null) {
            next.approve(request);
        } else {
            System.out.println("❌ " + request.getName()
                + " 你这是请假还是辞职?");
        }
    }
}

Step 4:客户端拼装

public class Client {
    public static void main(String[] args) {
        // 1. 先 new 出所有节点
        Approver teamLeader = new TeamLeader();
        Approver manager = new Manager();
        Approver director = new Director();

        // 2. 拼成链:组长 → 经理 → 总监
        teamLeader.setNext(manager).setNext(director);

        // 3. 员工提交请求,只管扔给链头
        teamLeader.approve(new LeaveRequest("张三", 1, "感冒"));
        teamLeader.approve(new LeaveRequest("李四", 5, "婚礼"));
        teamLeader.approve(new LeaveRequest("王五", 15, "出国"));
    }
}

输出:

✅ 组长 批了 张三 的 1 天假
✅ 经理 批了 李四 的 5 天假
✅ 总监 批了 王五 的 15 天假

是不是很清晰?员工不用关心"我这个假该找谁",直接交给链头就行。


版本二:王者版(企业级 - 订单审核)

青铜版能跑,但生产环境你会发现:

  • ❌ 处理器是写死的 new
  • ❌ 顺序硬编码
  • ❌ 每个处理器重复 if-else 判断
  • ❌ 想插一个新节点?得改代码

优化思路:用 Spring + 模板方法 + 责任链,做成"可配置"的责任链。

Step 1:处理器接口

/**
 * 订单处理器 - 顶层接口
 */
public interface OrderHandler {
    /**
     * 处理订单
     * @param order 订单
     * @return true=继续往下传,false=终止链
     */
    boolean handle(Order order);
}

Step 2:抽象模板(消除重复 if-else)

/**
 * 抽象处理器 - 模板方法模式 + 责任链
 * 把"判断 + 传递"逻辑抽到父类,子类只关心"自己能处理啥"
 */
public abstract class AbstractOrderHandler implements OrderHandler {

    @Getter
    private final String name;

    public AbstractOrderHandler(String name) {
        this.name = name;
    }

    /**
     * 模板方法:处理流程固定
     */
    @Override
    public final boolean handle(Order order) {
        // 前置日志
        log.info("🔗 [{}] 开始处理订单 {}", name, order.getId());

        // 1. 执行自己的处理逻辑
        boolean passed = doHandle(order);

        if (!passed) {
            log.warn("❌ [{}] 拦截订单 {}", name, order.getId());
            // 拦截后也要执行后置,避免资源泄漏
            afterHandle(order, false);
            return false;  // 终止链
        }

        // 2. 找下一个节点
        OrderHandler next = next();
        boolean nextResult = (next != null) ? next.handle(order) : true;

        // 3. 后置处理(无论成功失败都执行)
        afterHandle(order, nextResult);

        return nextResult;
    }

    /** 子类实现:具体的处理逻辑 */
    protected abstract boolean doHandle(Order order);

    /** 子类实现:下一个节点是谁 */
    protected abstract OrderHandler next();

    /** 钩子方法:后置处理,默认空实现 */
    protected void afterHandle(Order order, boolean success) {
        // 默认啥也不干,子类按需重写
    }
}

Step 3:具体处理器

/** 风控检查 */
@Component
public class RiskHandler extends AbstractOrderHandler {
    public RiskHandler() { super("风控检查"); }

    @Override
    protected boolean doHandle(Order order) {
        // 业务:检查账号是否在黑名单
        return !blackListService.isBlack(order.getUserId());
    }

    @Override
    protected OrderHandler next() {
        return SpringContextHolder.getBean(InventoryHandler.class);
    }
}

/** 库存检查 */
@Component
public class InventoryHandler extends AbstractOrderHandler {
    public InventoryHandler() { super("库存检查"); }

    @Override
    protected boolean doHandle(Order order) {
        return inventoryService.check(order.getSkuId(), order.getCount());
    }

    @Override
    protected OrderHandler next() {
        return SpringContextHolder.getBean(PayHandler.class);
    }
}

/** 支付检查 */
@Component
public class PayHandler extends AbstractOrderHandler {
    public PayHandler() { super("支付检查"); }

    @Override
    protected boolean doHandle(Order order) {
        return payService.isPaid(order.getOrderId());
    }

    @Override
    protected OrderHandler next() {
        return null;  // 我是链尾,没下家了
    }
}

Step 4:链的拼装(用配置类,顺序可配)

/**
 * 责任链工厂 - 链的"组装车间"
 */
@Configuration
public class OrderHandlerChainConfig {

    @Bean("orderHandlerChain")
    public OrderHandler orderHandlerChain(
            RiskHandler risk,
            InventoryHandler inventory,
            PayHandler pay) {

        // 链式组装:风控 → 库存 → 支付
        risk.setNext(inventory).setNext(pay);
        return risk;
    }
}

Step 5:使用

@Service
public class OrderService {

    @Resource(name = "orderHandlerChain")
    private OrderHandler orderHandlerChain;

    public void submit(Order order) {
        boolean success = orderHandlerChain.handle(order);
        if (success) {
            // 走到这里,说明整条链都通过了
            saveOrder(order);
        } else {
            // 任意一环失败
            log.info("订单 {} 审核被拒", order.getId());
        }
    }
}

新增一个审核环节?

写个新 Handler 继承 AbstractOrderHandler,改一下 next() 就行。完全不用动现有代码

这就是 "对扩展开放,对修改关闭" —— 开闭原则的教科书级应用。


五、Spring 中的责任链(你以为你没写过,其实天天用)

你以为责任链是面试题?No,Spring 全家桶到处都是。

1. Spring MVC 拦截器(HandlerInterceptor)

public interface HandlerInterceptor {
    boolean preHandle(...);     // 1. 前置:可以拦截
    void postHandle(...);       // 2. 后置:Controller 执行完
    void afterCompletion(...);  // 3. 最终:视图渲染完
}

preHandle 返回 false?后面的拦截器和 Controller 都不执行。

这不就是责任链么?

2. Spring Security 过滤器链

Spring Security 那 WebSecurityConfigurerAdapter 里配的:

http.addFilter(new CORSFilter())
    .addFilter(new JwtAuthFilter())
    .addFilter(new RateLimitFilter())
    .addFilter(new AuditLogFilter());

**一个请求过来,依次过这堆 Filter,**就是教科书级的责任链。

3. Servlet Filter

public class LogFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
        log.info("请求进来");
        chain.doFilter(req, res);  // 传给下一个
        log.info("请求出去");
    }
}

chain.doFilter() 这一行,就是责任链的"传球"动作。

4. Netty 的 ChannelPipeline

Netty 里数据从入站到出站,要过 N 个 ChannelHandler,每个都能拦截、处理、传给下一个。这也是责任链


六、责任链 vs 策略模式 vs 装饰器模式

面试高频混淆点,一张表搞清

维度责任链模式策略模式装饰器模式
核心目的不知道谁来处理,让一串人试试选一个算法给对象加功能
结构链式传递持有多个可替换策略包装另一个对象
典型场景审批、过滤器支付方式、排序算法IO 流包装、动态加日志
客户端关注不关心谁处理关心选哪个策略关心叠加后效果
代码特征每个 handler 都有 nextContext 持有 Strategy构造函数传入同类对象

一句话总结:

  • 责任链:这事儿该谁干?挨个问一圈
  • 策略:这事儿用啥法子干?挑一个
  • 装饰器:这事儿干完了,再加点料

七、避坑指南(我踩过的 7 个坑)

坑 1:链没终止,导致死循环

// ❌ 错误:A 的 next 是 B,B 的 next 又指向 A
a.setNext(b);
b.setNext(a);

解法:链尾的 next() 必须返回 null

坑 2:忘了处理"没人能处理"的情况

} else if (next != null) {
    next.approve(request);
}
// ❌ 如果 next 为 null,请求就这么丢了

解法

} else {
    throw new NoHandlerException("无人处理: " + request);
}

坑 3:链顺序写错,业务全乱

限流应该放在鉴权前面还是后面

  • 放前面:未鉴权的恶意请求直接被限流,省资源。
  • 放后面:只对真实用户限流,限流更精准。

**场景不同,顺序不同,**没有标准答案。

坑 4:性能问题

责任链是链式调用,节点越多性能越差。

建议

  • 高频链路控制在 5~8 个节点以内
  • 简单校验(如参数非空)放链头,复杂业务放链尾。
  • 高频请求考虑短路机制。

坑 5:日志混乱

10 个节点每个都打 5 行日志,定位问题像大海捞针。

建议:链路 traceId 一打通到底。

@Slf4j
public abstract class AbstractHandler {
    public boolean handle(Request req) {
        MDC.put("traceId", req.getTraceId());
        log.info("[{}] 开始处理", name);
        // ...
    }
}

坑 6:线程安全问题

如果 Handler 是有状态的,且被多线程共用,别用成员变量

// ❌ 错误:count 是共享的
private int count = 0;

解法:状态从 Request 传,不要放 Handler 里。

坑 7:Spring Bean 注入问题

责任链里 next() 怎么拿到下一个 Bean?

// ❌ 直接 @Resource 注入会有循环依赖
@Resource
private InventoryHandler inventory;

// ✅ 推荐:用 ApplicationContext 或自定义注解顺序
@Order(1)
@Component
public class RiskHandler { ... }

八、何时该用责任链?三句话判断

  • 一个请求需要多个人依次处理(审批、风控、过滤)
  • 处理顺序需要灵活调整
  • 想让每个处理节点独立测试

别用

  • 只有 1-2 个处理环节 → 直接调方法
  • 处理逻辑简单固定 → if-else 够了
  • 链太长(>10 个)→ 考虑拆分或异步

九、总结(一张图带走)

                ┌──────────────────┐
                │   Client 客户端  │
                └────────┬─────────┘
                         │ 发起请求
                         ▼
              ┌────────────────────┐
              │  HandlerA 节点1    │──┐
              │  - 能处理就处理    │  │ 不能处理
              │  - 不能就 next()  │◄─┘
              └────────┬───────────┘
                       │ next
                       ▼
              ┌────────────────────┐
              │  HandlerB 节点2    │
              └────────┬───────────┘
                       │ next
                       ▼
                       ...
                       ▼
              ┌────────────────────┐
              │  HandlerN 链尾     │
              │  next() = null     │
              └────────────────────┘

🙏 作者介绍

📌 写文不易,Bug 更不易。

如果这篇文章对你有帮助,可以搜一搜:空门技术栈

这里分享:

  • ✅ Java / Spring AI / 企业级项目实战
  • ✅ Docker / RAG知识库 / 微服务踩坑
  • ✅ Python、前端、AI应用落地
  • ✅ 偶尔分享一些「头发保卫战」经验 😆

一个热爱技术、持续填坑的开发者, 陪你一起少踩坑,少加班,多写优雅代码。

📖 推荐阅读


🤝 技术交流 / 项目合作

平时也会做一些技术项目与咨询,包括:

  • Java / Spring Boot 企业级项目开发
  • AI 应用开发(LangChain、RAG、Agent、知识库)
  • Docker / Linux / 私有化部署
  • 系统功能开发、接口对接、性能优化
  • 疑难问题排查与技术咨询

如果你:

  • 想做 AI 项目,但不确定技术方案
  • 项目卡在某个 Bug 很久
  • 想把 AI 接入现有系统
  • 需要企业级开发支持

欢迎交流。

📮 联系方式:

  • Email:2929119150@qq.com
  • 也可以私信我
  • 技术交流可通过个人主页联系

有些坑,一个人踩是事故;一起踩,就是经验 😎