设计模式-责任链模式

文章目录

  • 一、概述
    • [1.1 结构与角色](#1.1 结构与角色)
    • [1.2 适用场景](#1.2 适用场景)
  • 二、实现方式
    • [2.1 基础实现](#2.1 基础实现)
    • [2.2 纯责任链与非纯责任链](#2.2 纯责任链与非纯责任链)
    • [2.3 责任链模式 vs 策略模式](#2.3 责任链模式 vs 策略模式)
  • [三、Spring 拦截器链](#三、Spring 拦截器链)
    • [3.1 Spring 拦截器工作原理](#3.1 Spring 拦截器工作原理)
    • [3.2 实战示例------自定义拦截器链](#3.2 实战示例——自定义拦截器链)
  • 四、总结

一、概述

在软件开发中,经常会遇到这样的场景:一个请求需要经过多个处理环节,每个环节都有机会处理该请求,或者将其传递给下一个环节。例如,请假审批流程中,3 天以内由组长审批、3~7 天由经理审批、7 天以上由总监审批;Web 请求处理中,需要依次经过登录校验、权限校验、参数校验、日志记录等多个过滤器;订单处理中,需要依次经过库存检查、优惠计算、运费计算、支付处理等环节。如果将这些处理逻辑全部写在一个方法中,代码会变得臃肿、耦合度高、难以扩展
if 条件1
if 条件2
if 条件3
if 条件4
if 条件5
客户端
巨型处理方法
登录校验
权限校验
参数校验
业务处理
日志记录
每新增一个处理步骤就要修改核心方法

责任链模式(Chain of Responsibility Pattern)正是为了解决这个问题而诞生的------它将请求的发送者和接收者解耦,使多个对象都有机会处理这个请求,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。发送者无需知道哪个对象会处理请求,接收者之间也无需知道彼此的存在。

生活中的责任链模式例子:

  • 请假审批流程:员工请假 → 组长审批(3 天内)→ 经理审批(3~7 天)→ 总监审批(7 天以上),每个审批者只需判断自己能否处理,不能处理则交给上级
  • 快递分拣:快递到达分拣中心 → A 区处理 → B 区处理 → C 区处理,快件沿着传送带依次经过各区域,直到被对应区域处理
  • 击鼓传花:鼓声响起时花依次传递,鼓声停下时花在谁手里谁就"处理"它
  • Web 中间件链:HTTP 请求依次经过认证中间件、授权中间件、限流中间件、日志中间件,每个中间件都可以拦截请求或放行

核心:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止

1.1 结构与角色

责任链模式包含以下角色:
next.handle()
继承
继承
继承
Client 客户端
Handler 抽象处理器
ConcreteHandlerA 具体处理器A
ConcreteHandlerB 具体处理器B
ConcreteHandlerC 具体处理器C

  • Handler(抽象处理器):定义一个处理请求的接口(或抽象类),同时持有下一个处理器的引用,提供设置后继者的方法
  • ConcreteHandler(具体处理器):实现抽象处理器,处理它所负责的请求;如果不能处理,则将请求转发给后继者
  • Client(客户端):创建处理器对象,将它们组装成链,并向链头的处理器提交请求
  • 链的组装顺序:客户端负责决定处理器的排列顺序,链上的处理器无需感知整条链的结构

1.2 适用场景

  • 一个请求需要经过多个对象处理,且处理顺序可变、处理对象可动态增删
  • 希望在不明确指定接收者的情况下,向多个对象中的一个提交请求
  • 需要动态指定一组处理请求的对象,或者希望客户端无需了解具体的处理者
  • 常见的框架场景:Servlet Filter 链、Spring Interceptor 链、Netty ChannelPipeline、审批工作流等

二、实现方式

责任链模式的核心实现思路是:每个处理器持有一个指向后继处理器的引用,在 handle() 方法中判断自身能否处理,能处理则处理,不能处理则调用 next.handle() 将请求沿链传递。

2.1 基础实现

以"请假审批"为例,员工提交请假申请,根据请假天数由不同级别的管理者审批:

  • 组长:可审批 3 天以内的请假
  • 经理:可审批 3~7 天的请假
  • 总监:可审批 7 天以上的请假

next
next
继承
继承
继承
客户端
TeamLeader 组长
Manager 经理
Director 总监
Approver 抽象处理器

(1)抽象处理器------审批者

java 复制代码
/**
 * 抽象处理器:审批者
 * 持有下一个审批者的引用,定义审批流程的模板
 */
public abstract class Approver {

    /** 下一个审批者 */
    protected Approver next;

    /** 当前审批者的名称 */
    protected final String name;

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

    /**
     * 设置下一个审批者
     *
     * @param next 下一个审批者
     * @return 下一个审批者(支持链式组装)
     */
    public Approver setNext(Approver next) {
        this.next = next;
        return next;
    }

    /**
     * 处理请假请求(模板方法)
     * 如果能处理则处理,否则交给下一个审批者
     *
     * @param request 请假请求
     */
    public void handleRequest(LeaveRequest request) {
        if (canHandle(request)) {
            System.out.println(name + " 审批通过【" + request.getApplicant()
                    + "】的请假申请(" + request.getDays() + " 天)");
        } else if (next != null) {
            System.out.println(name + " 无法审批,转交 " + next.name);
            next.handleRequest(request);
        } else {
            System.out.println("请假申请无法处理:无人可审批 " + request.getDays() + " 天的请假");
        }
    }

    /**
     * 判断是否能处理当前请求(由子类实现)
     *
     * @param request 请假请求
     * @return 是否能处理
     */
    protected abstract boolean canHandle(LeaveRequest request);
}

(2)请假请求实体

java 复制代码
/**
 * 请假请求
 */
public class LeaveRequest {

    /** 申请人姓名 */
    private final String applicant;

    /** 请假天数 */
    private final int days;

    /** 请假原因 */
    private final String reason;

    public LeaveRequest(String applicant, int days, String reason) {
        this.applicant = applicant;
        this.days = days;
        this.reason = reason;
    }

    public String getApplicant() {
        return applicant;
    }

    public int getDays() {
        return days;
    }

    public String getReason() {
        return reason;
    }
}

(3)具体处理器------组长

java 复制代码
/**
 * 具体处理器:组长
 * 可审批 3 天以内的请假
 */
public class TeamLeader extends Approver {

    private static final int MAX_DAYS = 3;

    public TeamLeader(String name) {
        super(name);
    }

    @Override
    protected boolean canHandle(LeaveRequest request) {
        return request.getDays() <= MAX_DAYS;
    }
}

(4)具体处理器------经理

java 复制代码
/**
 * 具体处理器:经理
 * 可审批 3~7 天的请假
 */
public class Manager extends Approver {

    private static final int MAX_DAYS = 7;

    public Manager(String name) {
        super(name);
    }

    @Override
    protected boolean canHandle(LeaveRequest request) {
        return request.getDays() <= MAX_DAYS;
    }
}

(5)具体处理器------总监

java 复制代码
/**
 * 具体处理器:总监
 * 可审批 7 天以上的请假
 */
public class Director extends Approver {

    public Director(String name) {
        super(name);
    }

    @Override
    protected boolean canHandle(LeaveRequest request) {
        // 总监可以审批任意天数的请假
        return true;
    }
}

(6)客户端调用

java 复制代码
public class ChainDemo {
    public static void main(String[] args) {
        // 创建审批者
        Approver teamLeader = new TeamLeader("张组长");
        Approver manager    = new Manager("李经理");
        Approver director   = new Director("王总监");

        // 组装责任链:组长 → 经理 → 总监
        teamLeader.setNext(manager).setNext(director);

        // 提交请假申请
        LeaveRequest req1 = new LeaveRequest("小明", 2, "感冒看病");
        teamLeader.handleRequest(req1);
        // 张组长 审批通过【小明】的请假申请(2 天)

        LeaveRequest req2 = new LeaveRequest("小红", 5, "回老家参加婚礼");
        teamLeader.handleRequest(req2);
        // 张组长 无法审批,转交 李经理
        // 李经理 审批通过【小红】的请假申请(5 天)

        LeaveRequest req3 = new LeaveRequest("小刚", 10, "出国旅游");
        teamLeader.handleRequest(req3);
        // 张组长 无法审批,转交 李经理
        // 李经理 无法审批,转交 王总监
        // 王总监 审批通过【小刚】的请假申请(10 天)
    }
}

关键点 :每个处理器只需关注自身能否处理当前请求------能处理则处理,不能处理则交给下一个。客户端只与链头交互,无需知道链上有多少处理器以及各自的分工。新增一个审批级别只需新增一个处理器类并插入链中,符合开闭原则

2.2 纯责任链与非纯责任链

根据责任链中处理器对请求的"独占性",责任链分为两种模式:

对比维度 纯责任链 非纯责任链
处理方式 请求被链上某一个处理器处理 请求被链上多个处理器依次处理
传递控制 一旦处理,立即终止传递 处理完后继续传递给下一个
链尾行为 必须确保至少有一个处理器能处理 所有处理器都可能参与处理
典型场景 审批流程、异常捕获、击鼓传花 Filter 链、Interceptor 链、中间件链
结构复杂度 简单 较复杂,需要控制流程

纯责任链(前面的请假审批示例)------请求只在链上传递,直到有一个处理器处理它:
不能处理
不能处理
处理!
请求
处理器A
处理器B
处理器C
终止

非纯责任链------请求会依次经过链上的每一个处理器,每个处理器都可能对请求进行处理(或增强):
处理后传递
处理后传递
处理后传递
请求
处理器A
处理器B
处理器C
响应

以"敏感词过滤"为例演示非纯责任链------一条消息需要依次经过多个过滤器,每个过滤器负责过滤一类敏感词:

(1)处理器接口(非纯责任链,每个处理器都处理)

java 复制代码
/**
 * 过滤器接口(非纯责任链)
 * 每个过滤器对消息进行过滤处理,处理完后继续传递
 */
public interface Filter {

    /**
     * 过滤消息
     *
     * @param msg    原始消息
     * @param chain  过滤器链
     * @return 过滤后的消息
     */
    String doFilter(String msg, FilterChain chain);
}

(2)过滤器链管理

java 复制代码
import java.util.ArrayList;
import java.util.List;

/**
 * 过滤器链(非纯责任链)
 * 管理所有过滤器,按顺序依次调用
 */
public class FilterChain {

    private final List<Filter> filters = new ArrayList<>();
    private int index = 0;

    /**
     * 添加过滤器到链中
     *
     * @param filter 过滤器
     * @return 当前链(支持链式添加)
     */
    public FilterChain addFilter(Filter filter) {
        filters.add(filter);
        return this;
    }

    /**
     * 执行过滤链
     * 按索引顺序依次调用每个过滤器
     *
     * @param msg 原始消息
     * @return 过滤后的消息
     */
    public String doFilter(String msg) {
        if (index < filters.size()) {
            Filter filter = filters.get(index++);
            return filter.doFilter(msg, this);
        }
        return msg;
    }

    /**
     * 重置索引(支持链的复用)
     */
    public void reset() {
        index = 0;
    }
}

(3)具体过滤器------政治敏感词过滤

java 复制代码
/**
 * 具体过滤器:政治敏感词过滤
 */
public class PoliticalFilter implements Filter {

    @Override
    public String doFilter(String msg, FilterChain chain) {
        // 过滤敏感词
        msg = msg.replace("敏感词A", "***")
                 .replace("敏感词B", "***");
        // 处理完后继续传递给下一个过滤器
        return chain.doFilter(msg);
    }
}

(4)具体过滤器------色情敏感词过滤

java 复制代码
/**
 * 具体过滤器:色情敏感词过滤
 */
public class PornFilter implements Filter {

    @Override
    public String doFilter(String msg, FilterChain chain) {
        msg = msg.replace("色情词A", "***")
                 .replace("色情词B", "***");
        return chain.doFilter(msg);
    }
}

(5)具体过滤器------广告敏感词过滤

java 复制代码
/**
 * 具体过滤器:广告敏感词过滤
 */
public class AdFilter implements Filter {

    @Override
    public String doFilter(String msg, FilterChain chain) {
        msg = msg.replace("广告词A", "***")
                 .replace("广告词B", "***");
        return chain.doFilter(msg);
    }
}

(6)客户端调用

java 复制代码
public class FilterChainDemo {
    public static void main(String[] args) {
        // 创建过滤器链(非纯责任链)
        FilterChain chain = new FilterChain();
        chain.addFilter(new PoliticalFilter())
             .addFilter(new PornFilter())
             .addFilter(new AdFilter());

        String msg = "这是一条包含敏感词A和色情词A以及广告词A的消息";
        String result = chain.doFilter(msg);
        System.out.println("过滤前:" + msg);
        System.out.println("过滤后:" + result);
        // 过滤前:这是一条包含敏感词A和色情词A以及广告词A的消息
        // 过滤后:这是一条包含***和***以及***的消息
    }
}

纯 vs 非纯的选择 :审批类场景(一个请求只需一个处理器处理)使用纯责任链;过滤/增强类场景(请求需要被多个处理器依次处理)使用非纯责任链。实际开发中非纯责任链的使用频率更高,因为大多数框架(Servlet Filter、Spring Interceptor)都采用这种模式。

2.3 责任链模式 vs 策略模式

责任链模式和策略模式都涉及"多个处理方式的选择",容易混淆:

对比维度 责任链模式 策略模式
核心目的 让多个对象依次尝试处理请求 从多个算法中显式选择一个执行
处理者数量 可能有多个处理器参与 每次只有一个策略执行
选择方式 处理器自行判断能否处理 客户端显式选择策略
客户端感知 客户端不知道谁会处理 客户端明确知道使用哪个策略
链结构 有明确的链式结构(next 引用) 无链式结构(Context 直接持有 Strategy)
典型场景 审批流程、过滤器链、异常捕获 支付方式、排序算法、出行方式

简单记忆 :责任链是"我处理不了,你来 ",策略是"我选择用哪个算法"。前者是被动的尝试,后者是主动的选择。


三、Spring 拦截器链

Spring 框架中的拦截器(Interceptor)是责任链模式在 Java Web 开发中最经典、最常用的应用。Spring MVC 的拦截器链采用非纯责任链 模式------请求依次经过每个拦截器的 preHandle(),任何一个拦截器返回 false 都会截断链的传递。

3.1 Spring 拦截器工作原理

Spring MVC 拦截器基于 HandlerInterceptor 接口,提供了三个拦截点:
返回 true
返回 true
返回 true
返回 false 截断
客户端请求
DispatcherServlet
preHandle-1 拦截器1
preHandle-2 拦截器2
preHandle-3 拦截器3
Controller 处理器
postHandle-3
postHandle-2
postHandle-1
afterCompletion-3
afterCompletion-2
afterCompletion-1
响应返回客户端
直接返回

  • preHandle :在 Controller 方法执行前调用,返回 true 放行,返回 false 截断链
  • postHandle:在 Controller 方法执行后、视图渲染前调用
  • afterCompletion:在整个请求完成后(视图渲染后)调用,通常用于资源清理

Spring 内部通过 HandlerExecutionChain 管理拦截器链:

java 复制代码
// Spring 源码简化示意
public class HandlerExecutionChain {

    private final Object handler;
    
    @Nullable
    private HandlerInterceptor[] interceptors;
    
    // 按注册顺序依次调用 preHandle
    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) 
            throws Exception {
        for (int i = 0; i < this.interceptors.length; i++) {
            HandlerInterceptor interceptor = this.interceptors[i];
            if (!interceptor.preHandle(request, response, this.handler)) {
                // 任何一个拦截器返回 false,触发已执行拦截器的 afterCompletion
                triggerAfterCompletion(request, response, null);
                return false;
            }
        }
        return true;
    }
}

这就是典型的非纯责任链------请求依次经过每个拦截器,每个拦截器都有机会处理(增强/拦截)请求,处理完后传递给下一个。

3.2 实战示例------自定义拦截器链

以"API 请求防护"为例,设计三个拦截器:登录校验、权限校验、限流校验,请求必须依次通过这三个拦截器才能到达 Controller。

(1)登录校验拦截器

java 复制代码
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 登录校验拦截器
 * 检查请求是否携带有效 token,未登录则拦截
 */
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, 
                             HttpServletResponse response, 
                             Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        if (token == null || token.isEmpty()) {
            response.setStatus(401);
            response.getWriter().write("{\"code\":401,\"msg\":\"请先登录\"}");
            // 返回 false 截断链,后续拦截器不再执行
            return false;
        }
        // 模拟 token 校验
        if (!isValidToken(token)) {
            response.setStatus(401);
            response.getWriter().write("{\"code\":401,\"msg\":\"token 已过期\"}");
            return false;
        }
        System.out.println("LoginInterceptor:登录校验通过");
        // 返回 true 放行,传递给下一个拦截器
        return true;
    }

    private boolean isValidToken(String token) {
        // 实际项目中调用认证服务校验 token
        return token.startsWith("Bearer ");
    }
}

(2)权限校验拦截器

java 复制代码
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 权限校验拦截器
 * 检查用户是否有访问当前接口的权限
 */
public class PermissionInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        // 从请求中获取用户角色(实际项目中从 token 或 session 中获取)
        String role = request.getHeader("X-User-Role");
        String uri = request.getRequestURI();

        // 管理员接口只允许 admin 角色访问
        if (uri.startsWith("/admin") && !"admin".equals(role)) {
            response.setStatus(403);
            response.getWriter().write("{\"code\":403,\"msg\":\"权限不足\"}");
            return false;
        }
        System.out.println("PermissionInterceptor:权限校验通过(角色:" + role + ")");
        return true;
    }
}

(3)限流校验拦截器

java 复制代码
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 限流校验拦截器
 * 基于滑动窗口的简单限流,限制每个 IP 每秒的请求次数
 */
public class RateLimitInterceptor implements HandlerInterceptor {

    /** IP → 请求计数器 */
    private final Map<String, AtomicInteger> counterMap = new ConcurrentHashMap<>();

    /** 每秒最大请求数 */
    private static final int MAX_REQUESTS_PER_SECOND = 10;

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        String clientIp = getClientIp(request);
        AtomicInteger counter = counterMap.computeIfAbsent(
                clientIp, k -> new AtomicInteger(0));

        int currentCount = counter.incrementAndGet();
        if (currentCount > MAX_REQUESTS_PER_SECOND) {
            response.setStatus(429);
            response.getWriter().write("{\"code\":429,\"msg\":\"请求过于频繁,请稍后再试\"}");
            return false;
        }
        System.out.println("RateLimitInterceptor:限流通过(IP:" + clientIp
                + ",请求数:" + currentCount + ")");
        return true;
    }

    /**
     * 获取客户端真实 IP
     */
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /**
     * 请求完成后递减计数器(afterCompletion 中调用)
     */
    public void decrementCounter(String clientIp) {
        AtomicInteger counter = counterMap.get(clientIp);
        if (counter != null) {
            counter.decrementAndGet();
        }
    }
}

(4)注册拦截器

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * Spring MVC 配置------注册拦截器链
 * 拦截器按注册顺序执行,形成责任链
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/api/**")       // 拦截所有 API 请求
                .excludePathPatterns("/api/login", "/api/register")  // 排除登录注册
                .order(1);  // 最先执行

        registry.addInterceptor(new PermissionInterceptor())
                .addPathPatterns("/api/**")
                .order(2);  // 第二执行

        registry.addInterceptor(new RateLimitInterceptor())
                .addPathPatterns("/api/**")
                .order(3);  // 第三执行
    }
}

关键点 :Spring 拦截器的执行顺序由 order() 值决定(值越小越先执行),这与责任链模式中客户端控制链组装顺序的思想完全一致。拦截器的 preHandle() 返回 false 时会截断链的传递------这正是责任链模式中"某个处理器处理完毕,不再向后传递"的体现。


四、总结

责任链模式是行为型设计模式中使用频率极高的模式之一,它将请求的发送者和接收者解耦,使多个处理器都有机会处理请求。本文围绕责任链模式的核心思想,从结构、实现到框架应用进行了系统讲解:

章节 核心内容
一、概述 介绍了责任链模式的定义、结构和适用场景,通过请假审批、快递分拣等生活场景帮助理解
二、实现方式 以请假审批为例演示了纯责任链,以敏感词过滤为例演示了非纯责任链,并对比了两种模式的区别
三、Spring 拦截器链 深入分析了 Spring MVC 拦截器链的责任链本质,提供了登录校验、权限校验、限流校验的完整实战示例

责任链模式的核心优势:

  • 解耦发送者与接收者:客户端只需将请求提交给链头,无需知道链上有哪些处理器以及处理规则
  • 动态组装链:可以在运行时动态增删处理器、调整处理顺序,极大提升灵活性
  • 单一职责:每个处理器只关注自己的处理逻辑,职责清晰,便于维护和测试
  • 符合开闭原则:新增处理器无需修改已有处理器和客户端代码
优点 缺点
降低耦合度,发送者与接收者解耦 链过长时可能影响性能
增强灵活性,支持动态增删处理器 调试困难,请求在链上的流转路径不直观
符合单一职责原则,每个处理器职责清晰 纯责任链中可能没有处理器处理请求(链尾需兜底)
符合开闭原则,新增处理器无需修改已有代码 链的组装顺序对结果有决定性影响,需谨慎设计

在实际开发中,责任链模式广泛应用于:Servlet Filter 链、Spring Interceptor 链、Netty ChannelPipeline、日志框架(Logback/SLF4J)的 Appender 链、审批工作流引擎等。掌握责任链模式,不仅能提升代码的扩展性和可维护性,还能深入理解各类框架的底层设计思想。

相关推荐
多加点辣也没关系2 小时前
设计模式-命令模式
设计模式·命令模式
benpaodeDD4 小时前
视频49——设计模式之责任链模式
设计模式·责任链模式
草莓熊Lotso4 小时前
【Linux系统加餐】从原理到实战:System V消息队列全解析 + 基于责任链模式的工业级封装
linux·运维·服务器·c语言·c++·人工智能·责任链模式
雪度娃娃4 小时前
行为型设计模式——迭代器模式
c++·设计模式·迭代器模式
踩着两条虫4 小时前
可视化设计器组件系统:从交互核心到 AI 智能代理的落地实践
开发语言·前端·人工智能·低代码·设计模式·架构
nnsix17 小时前
设计模式 - 模板方法模式 笔记
笔记·设计模式·模板方法模式
likerhood21 小时前
设计模式-装饰器模式(java)
java·设计模式·装饰器模式
月落归舟1 天前
深入理解责任链模式:从原理到实战
责任链模式
青瓦梦滋1 天前
C++特殊类设计(设计模式)和类型转换
c++·设计模式