责任链模式(Chain of Responsibility)
你在写一个 HTTP 接口时,是否曾把「先记日志、再校验参数、再鉴权、再限流、最后进业务」全塞进一个 Controller 或一大串 if-else?一旦要加一步或调顺序,就要改同一块代码,既难维护也难测试。
责任链模式 (Chain of Responsibility) 正是用来把这类「多步处理、顺序固定、可增删步骤」的逻辑拆成一条链,让请求依次经过多个处理者,谁该处理谁处理,其余交给下一个。
一、为什么需要责任链?
Section titled “一、为什么需要责任链?”1.1 要解决的问题
Section titled “1.1 要解决的问题”- 请求只需被链中某一个(或某几个)处理者处理,但你不希望在发送方写死「到底谁处理」——否则会变成一长串
if-else或switch,每加一种处理方式就要改调用方。 - 处理步骤需要动态增减、顺序可调,且调用方不应依赖具体处理者实现,只依赖「一条链」的入口。
典型场景包括:审批流、事件/请求的预处理管道、日志/审计/风控等多道关卡、告警的多渠道下发等。共同点是:多道工序、顺序固定、易扩展。
1.2 解决思路(用「链」解耦)
Section titled “1.2 解决思路(用「链」解耦)”- 定义抽象处理者 (Handler):持有「下一处理者」的引用,提供统一的「处理请求」方法;若当前节点不处理,则把请求转给
next。 - 具体处理者 (ConcreteHandler):实现自己的处理逻辑,能处理就处理并结束,否则调用
next.handleRequest(...)。 - 客户端只负责把多个处理者串成链(通过
setNext等),然后只对链头发起一次请求,请求会沿链传递,直到某节点处理或到链尾。
这样,发送方只认识「链头」;谁先谁后、加谁减谁,都由「组装链」的一方决定,符合开闭原则。
二、结构概览
Section titled “二、结构概览”抽象处理者持有一个 next(类型为 Handler),指向链中下一个处理者;下面类图中的 Handler o-- Handler : next 表示这种自引用关系,运行时由具体子类实例组成链。
classDiagram
class Handler {
<<abstract>>
#next Handler
+setNext(Handler h) void
+handleRequest(request)* void
}
class ConcreteHandlerA {
+handleRequest(request) void
}
class ConcreteHandlerB {
+handleRequest(request) void
}
Handler <|-- ConcreteHandlerA
Handler <|-- ConcreteHandlerB
Handler o-- Handler : next
运行时链(客户端组装后)可以理解为:
flowchart LR
A[ConcreteHandlerA] --> B[ConcreteHandlerB]
B -.-> C[...]
请求从 A 进入,A 不处理则交给 B,以此类推。
三、责任链的顺序从何而来?
Section titled “三、责任链的顺序从何而来?”责任链的顺序完全由「组装链」的一方决定,模式本身没有内置「优先级规则」,而是通过谁先被 setNext、谁作为链头来固定顺序。
- 谁先被 setNext,谁就在链的前面:例如先执行
a.setNext(b),请求会先从a进入,a不处理再交给b。若改成b.setNext(a),链头就变成b,顺序相反。 - 链头 = 唯一入口:客户端只对链头调用
handleRequest,请求沿next依次往后传,直到某节点处理或到链尾。 - 追加新节点:不需要改已有节点代码,只在组装时继续
setNext即可。若希望可读性更好,可以让setNext返回当前节点或链尾,支持链式调用(如a.setNext(b).setNext(c)),或在封装类里维护链尾再在链尾追加。
因此,「保证顺序」的本质是:在组装链时按想要的顺序依次 setNext,请求就会按该顺序经过各处理者。
public abstract class Handler { protected Handler next;
public void setNext(Handler next) { this.next = next; }
public abstract void handleRequest(String request);}
public class ConcreteHandlerA extends Handler { @Override public void handleRequest(String request) { if ("A".equals(request)) { System.out.println("A 处理"); return; } if (next != null) next.handleRequest(request); }}
public class ConcreteHandlerB extends Handler { @Override public void handleRequest(String request) { if ("B".equals(request)) { System.out.println("B 处理"); return; } if (next != null) next.handleRequest(request); }}
// 客户端Handler a = new ConcreteHandlerA();Handler b = new ConcreteHandlerB();a.setNext(b);a.handleRequest("B"); // 输出: B 处理接下来在 Spring Boot 里用两种场景把这条链「做实」:请求处理管道 + 告警通知链。
五、API 请求处理管道
Section titled “五、API 请求处理管道”很多接口都需要:先记录请求日志 → 再校验参数 → 再认证/鉴权 → 再限流 → 最后进业务。用责任链可以把每一步拆成一个 Handler,在 Spring 里用 Bean 组装成链,便于单独测试和调整顺序。
5.1 请求上下文与抽象 Handler
Section titled “5.1 请求上下文与抽象 Handler”先定义请求上下文(可随项目扩展字段),以及抽象处理者接口。
package com.example.demo.chain;
import lombok.Builder;import lombok.Data;
@Data@Builderpublic class RequestContext { private String requestId; private String path; private String token; private Object body; /** 是否已中止(如校验失败、未认证),后续节点可不再处理 */ private boolean aborted; /** 中止原因,便于日志或返回 */ private String abortReason;}package com.example.demo.chain;
/** * 抽象处理者:能处理则处理并可能设置 aborted,否则交给 next。 */public interface RequestHandler { /** 返回下一节点,用于组装链 */ RequestHandler getNext(); void setNext(RequestHandler next);
void handle(RequestContext ctx);}5.2 抽象基类(封装「交给下一节点」)
Section titled “5.2 抽象基类(封装「交给下一节点」)”package com.example.demo.chain;
import lombok.Getter;import lombok.Setter;
@Getter@Setterpublic abstract class AbstractRequestHandler implements RequestHandler { private RequestHandler next;
@Override public void handle(RequestContext ctx) { if (ctx.isAborted()) { if (next != null) next.handle(ctx); return; } doHandle(ctx); if (next != null) next.handle(ctx); }
/** 子类实现:当前节点的逻辑,可设置 ctx.setAborted(true) 表示不再继续业务 */ protected abstract void doHandle(RequestContext ctx);}5.3 具体处理者示例:日志、校验、认证
Section titled “5.3 具体处理者示例:日志、校验、认证”package com.example.demo.chain;
import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;
@Slf4j@Componentpublic class LoggingHandler extends AbstractRequestHandler { @Override protected void doHandle(RequestContext ctx) { log.info("requestId={}, path={}", ctx.getRequestId(), ctx.getPath()); }}package com.example.demo.chain;
import org.springframework.stereotype.Component;
@Componentpublic class ValidationHandler extends AbstractRequestHandler { @Override protected void doHandle(RequestContext ctx) { if (ctx.getBody() == null) { ctx.setAborted(true); ctx.setAbortReason("body required"); } }}package com.example.demo.chain;
import org.springframework.stereotype.Component;
@Componentpublic class AuthHandler extends AbstractRequestHandler { @Override protected void doHandle(RequestContext ctx) { if (ctx.getToken() == null || ctx.getToken().isBlank()) { ctx.setAborted(true); ctx.setAbortReason("unauthorized"); } }}5.4 组装链并注入使用
Section titled “5.4 组装链并注入使用”用配置类按顺序组装整条链,链头注入给 Controller 或 Service 使用。
package com.example.demo.chain;
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;
@Configurationpublic class RequestChainConfig {
@Bean public RequestHandler requestHandlerChain(LoggingHandler logging, ValidationHandler validation, AuthHandler auth) { logging.setNext(validation); validation.setNext(auth); // auth 之后可以再接限流、业务 Handler 等 return logging; }}package com.example.demo.web;
import com.example.demo.chain.RequestContext;import com.example.demo.chain.RequestHandler;import org.springframework.web.bind.annotation.*;
@RestController@RequestMapping("/api")public class DemoController { private final RequestHandler requestHandlerChain;
public DemoController(RequestHandler requestHandlerChain) { this.requestHandlerChain = requestHandlerChain; }
@PostMapping("/demo") public String demo(@RequestHeader(value = "X-Request-Id", required = false) String requestId, @RequestHeader(value = "Authorization", required = false) String token, @RequestBody(required = false) Object body) { RequestContext ctx = RequestContext.builder() .requestId(requestId != null ? requestId : "unknown") .path("/api/demo") .token(token) .body(body) .build(); requestHandlerChain.handle(ctx); if (ctx.isAborted()) { return "error: " + ctx.getAbortReason(); } return "ok"; }}这样,日志 → 校验 → 认证 的顺序由 RequestChainConfig 里的一次 setNext 决定,新增或调整节点只需改配置类,符合责任链的扩展方式。
六、告警通知链(多渠道下发)
Section titled “六、告警通知链(多渠道下发)”另一个典型场景是告警/事件需要依次尝试多种通知方式:例如先打日志(控制台),再发邮件,再发企业微信。每条渠道是一个 Handler,按顺序组成链;某个渠道发送成功可以选择结束链,也可以选择「无论成败都继续下一渠道」——按你的业务决定即可。下面示例采用「依次尝试,不短路」的写法。
6.1 通知上下文与抽象 Handler
Section titled “6.1 通知上下文与抽象 Handler”package com.example.demo.notify;
import lombok.Builder;import lombok.Data;
@Data@Builderpublic class NotifyContext { private String title; private String message; private String level; // INFO, WARN, ERROR /** 是否已有渠道发送成功(可选:用于短路后续渠道) */ private boolean delivered;}package com.example.demo.notify;
public interface NotifyHandler { NotifyHandler getNext(); void setNext(NotifyHandler next); void handle(NotifyContext ctx);}6.2 抽象基类与具体渠道
Section titled “6.2 抽象基类与具体渠道”package com.example.demo.notify;
import lombok.Getter;import lombok.Setter;
@Getter@Setterpublic abstract class AbstractNotifyHandler implements NotifyHandler { private NotifyHandler next;
@Override public void handle(NotifyContext ctx) { doHandle(ctx); if (getNext() != null) getNext().handle(ctx); }
protected abstract void doHandle(NotifyContext ctx);}package com.example.demo.notify;
import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Component;
@Slf4j@Componentpublic class ConsoleNotifyHandler extends AbstractNotifyHandler { @Override protected void doHandle(NotifyContext ctx) { log.warn("[告警] {} | {} | {}", ctx.getLevel(), ctx.getTitle(), ctx.getMessage()); }}package com.example.demo.notify;
import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.mail.SimpleMailMessage;import org.springframework.mail.javamail.JavaMailSender;import org.springframework.stereotype.Component;
@Slf4j@Component@RequiredArgsConstructorpublic class EmailNotifyHandler extends AbstractNotifyHandler { private final JavaMailSender mailSender;
@Override protected void doHandle(NotifyContext ctx) { try { SimpleMailMessage msg = new SimpleMailMessage(); msg.setSubject(ctx.getTitle()); msg.setText(ctx.getMessage()); mailSender.send(msg); ctx.setDelivered(true); } catch (Exception e) { log.error("email notify failed", e); } }}package com.example.demo.notify;
import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import org.springframework.web.client.RestTemplate;
@Slf4j@Component@RequiredArgsConstructorpublic class WeComNotifyHandler extends AbstractNotifyHandler { private final RestTemplate restTemplate; @Value("${app.wecom.webhook-url:}") private String webhookUrl;
@Override protected void doHandle(NotifyContext ctx) { if (webhookUrl == null || webhookUrl.isBlank()) return; try { String body = String.format("{\"msgtype\":\"text\",\"text\":{\"content\":\"%s: %s\"}}", ctx.getTitle(), ctx.getMessage().replace("\"", "\\\"")); restTemplate.postForObject(webhookUrl, body, String.class); ctx.setDelivered(true); } catch (Exception e) { log.error("wecom notify failed", e); } }}6.5 组装告警链并调用
Section titled “6.5 组装告警链并调用”package com.example.demo.notify;
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;
@Configurationpublic class NotifyChainConfig {
@Bean public NotifyHandler notifyHandlerChain(ConsoleNotifyHandler console, EmailNotifyHandler email, WeComNotifyHandler weCom) { console.setNext(email); email.setNext(weCom); return console; }}业务侧只需注入链头,调用一次 handle 即可走完「控制台 → 邮件 → 企业微信」:
@Service@RequiredArgsConstructorpublic class AlertService { private final NotifyHandler notifyHandlerChain;
public void sendAlert(String title, String message, String level) { NotifyContext ctx = NotifyContext.builder() .title(title) .message(message) .level(level) .build(); notifyHandlerChain.handle(ctx); }}七、小结与使用建议
Section titled “七、小结与使用建议”- 责任链通过「链式传递」把请求的发送者和多个处理者解耦,顺序由组装链时谁先 setNext 决定,便于动态增删节点、调顺序,符合开闭原则。
- 在 Spring Boot 中:
- 用
@Component定义多个 Handler,在@Configuration里按顺序setNext组装成链,把链头作为 Bean 暴露出去。 - 请求处理管道(日志 → 校验 → 认证 → 限流 → 业务)、告警/通知多渠道下发,都是很贴合责任链的场景;
- 用
- 若某一步需要「处理了就不再往后传」,在上下文里设标志位(如
aborted、delivered),在基类handle里判断后不再调用next即可。
把一长串 if-else 或多层嵌套的预处理逻辑重构成管道,责任链是一个清晰、易测试、易扩展的实现方式;配合 Spring 的依赖注入,链的组装和替换都会很顺手。