跳转到内容

责任链模式(Chain of Responsibility)

你在写一个 HTTP 接口时,是否曾把「先记日志、再校验参数、再鉴权、再限流、最后进业务」全塞进一个 Controller 或一大串 if-else?一旦要加一步或调顺序,就要改同一块代码,既难维护也难测试。

责任链模式 (Chain of Responsibility) 正是用来把这类「多步处理、顺序固定、可增删步骤」的逻辑拆成一条链,让请求依次经过多个处理者,谁该处理谁处理,其余交给下一个。


  • 请求只需被链中某一个(或某几个)处理者处理,但你不希望在发送方写死「到底谁处理」——否则会变成一长串 if-elseswitch,每加一种处理方式就要改调用方。
  • 处理步骤需要动态增减、顺序可调,且调用方不应依赖具体处理者实现,只依赖「一条链」的入口。

典型场景包括:审批流、事件/请求的预处理管道、日志/审计/风控等多道关卡、告警的多渠道下发等。共同点是:多道工序、顺序固定、易扩展

  • 定义抽象处理者 (Handler):持有「下一处理者」的引用,提供统一的「处理请求」方法;若当前节点不处理,则把请求转给 next
  • 具体处理者 (ConcreteHandler):实现自己的处理逻辑,能处理就处理并结束,否则调用 next.handleRequest(...)
  • 客户端只负责把多个处理者串成链(通过 setNext 等),然后只对链头发起一次请求,请求会沿链传递,直到某节点处理或到链尾。

这样,发送方只认识「链头」;谁先谁后、加谁减谁,都由「组装链」的一方决定,符合开闭原则。


抽象处理者持有一个 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,以此类推。


责任链的顺序完全由「组装链」的一方决定,模式本身没有内置「优先级规则」,而是通过谁先被 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 里用两种场景把这条链「做实」:请求处理管道 + 告警通知链。


很多接口都需要:先记录请求日志 → 再校验参数 → 再认证/鉴权 → 再限流 → 最后进业务。用责任链可以把每一步拆成一个 Handler,在 Spring 里用 Bean 组装成链,便于单独测试和调整顺序。

先定义请求上下文(可随项目扩展字段),以及抽象处理者接口。

package com.example.demo.chain;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public 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
@Setter
public 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
@Component
public 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;
@Component
public 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;
@Component
public class AuthHandler extends AbstractRequestHandler {
@Override
protected void doHandle(RequestContext ctx) {
if (ctx.getToken() == null || ctx.getToken().isBlank()) {
ctx.setAborted(true);
ctx.setAbortReason("unauthorized");
}
}
}

用配置类按顺序组装整条链,链头注入给 Controller 或 Service 使用。

package com.example.demo.chain;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public 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,按顺序组成链;某个渠道发送成功可以选择结束链,也可以选择「无论成败都继续下一渠道」——按你的业务决定即可。下面示例采用「依次尝试,不短路」的写法。

package com.example.demo.notify;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public 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);
}
package com.example.demo.notify;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public 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
@Component
public 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
@RequiredArgsConstructor
public class EmailNotifyHandler extends AbstractNotifyHandler {
private final JavaMailSender mailSender;
@Override
protected void doHandle(NotifyContext ctx) {
try {
SimpleMailMessage msg = new SimpleMailMessage();
msg.setTo("[email protected]");
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
@RequiredArgsConstructor
public 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);
}
}
}
package com.example.demo.notify;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class NotifyChainConfig {
@Bean
public NotifyHandler notifyHandlerChain(ConsoleNotifyHandler console,
EmailNotifyHandler email,
WeComNotifyHandler weCom) {
console.setNext(email);
email.setNext(weCom);
return console;
}
}

业务侧只需注入链头,调用一次 handle 即可走完「控制台 → 邮件 → 企业微信」:

@Service
@RequiredArgsConstructor
public 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);
}
}

  • 责任链通过「链式传递」把请求的发送者和多个处理者解耦,顺序由组装链时谁先 setNext 决定,便于动态增删节点、调顺序,符合开闭原则。
  • Spring Boot 中:
    • @Component 定义多个 Handler,在 @Configuration 里按顺序 setNext 组装成链,把链头作为 Bean 暴露出去。
    • 请求处理管道(日志 → 校验 → 认证 → 限流 → 业务)、告警/通知多渠道下发,都是很贴合责任链的场景;
  • 若某一步需要「处理了就不再往后传」,在上下文里设标志位(如 aborteddelivered),在基类 handle 里判断后不再调用 next 即可。

把一长串 if-else 或多层嵌套的预处理逻辑重构成管道,责任链是一个清晰、易测试、易扩展的实现方式;配合 Spring 的依赖注入,链的组装和替换都会很顺手。