【设计模式】【行为型模式】职责链模式(Chain of Responsibility)
👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
🎵 当你的天空突然下了大雨,那是我在为你炸乌云
文章目录
- 一、入门
- 什么是职责链模式?
- 为什么需要职责链模式?
- 怎样实现职责链模式?
- 二、职责链模式在源码中的运用
- 2.1、Spring MVC 拦截器
- 2.1.1、如何使用SpringMVC拦截器?
- 2.1.2、SpringMVC拦截器是如何实现的?
- 2.2、Java Web的Filter Chain
- 三、总结
- 参考
一、入门
什么是职责链模式?
职责链模式是一种行为设计模式,它允许你将请求沿着一条链传递,直到有对象处理它为止。每个对象都有机会处理请求,或者将其传递给链中的下一个对象。
为什么需要职责链模式?
使用职责链模式的好处:
- 解耦:发送者和处理者不直接依赖,各自独立。
- 灵活:动态调整处理顺序,易于扩展。
- 清晰:每个对象只处理自己能做的事,职责单一。
- 避免硬编码:处理逻辑由链决定,代码更灵活。
怎样实现职责链模式?
责任链模式主要包含以下角色:
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接(下一个对象的引用)。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
【案例】请假审批
现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天到7天还需要总经理同意才行。

请假条
public class LeaveRequest {private String name;//姓名private int num;//请假天数private String content;//请假内容public LeaveRequest(String name, int num, String content) {this.name = name;this.num = num;this.content = content;}public String getName() {return name;}public int getNum() {return num;}public String getContent() {return content;}
}
处理者抽象类
public abstract class Handler {protected final static int NUM_ONE = 1;protected final static int NUM_THREE = 3;protected final static int NUM_SEVEN = 7;//该领导处理的请假天数区间private int numStart;private int numEnd;//领导上面还有领导private Handler nextHandler;//设置请假天数范围 上不封顶public Handler(int numStart) {this.numStart = numStart;}//设置请假天数范围public Handler(int numStart, int numEnd) {this.numStart = numStart;this.numEnd = numEnd;}//设置上级领导public void setNextHandler(Handler nextHandler){this.nextHandler = nextHandler;}//提交请假条public final void submit(LeaveRequest leave){if(0 == this.numStart){return;}//如果请假天数达到该领导者的处理要求if(leave.getNum() >= this.numStart){this.handleLeave(leave);//如果还有上级 并且请假天数超过了当前领导的处理范围if(null != this.nextHandler && leave.getNum() > numEnd){this.nextHandler.submit(leave);//继续提交} else {System.out.println("流程结束");}}}//各级领导处理请假条方法protected abstract void handleLeave(LeaveRequest leave);
}
小组长
public class GroupLeader extends Handler {public GroupLeader() {//小组长处理1-3天的请假super(Handler.NUM_ONE, Handler.NUM_THREE);}@Overrideprotected void handleLeave(LeaveRequest leave) {System.out.println(leave.getName() + "请假" + leave.getNum() +"天," + leave.getContent() + "。");System.out.println("小组长审批:同意。");}
}
部门经理
public class Manager extends Handler {public Manager() {//部门经理处理3-7天的请假super(Handler.NUM_THREE, Handler.NUM_SEVEN);}@Overrideprotected void handleLeave(LeaveRequest leave) {System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");System.out.println("部门经理审批:同意。");}
}
总经理
public class GeneralManager extends Handler {public GeneralManager() {//部门经理处理7天以上的请假super(Handler.NUM_SEVEN);}@Overrideprotected void handleLeave(LeaveRequest leave) {System.out.println(leave.getName() + "请假" + leave.getNum() + "天," + leave.getContent() + "。");System.out.println("总经理审批:同意。");}
}
测试
public class Client {public static void main(String[] args) {//请假条来一张LeaveRequest leave = new LeaveRequest("小花",5,"身体不适");//各位领导GroupLeader groupLeader = new GroupLeader();Manager manager = new Manager();GeneralManager generalManager = new GeneralManager();groupLeader.setNextHandler(manager);//小组长的领导是部门经理manager.setNextHandler(generalManager);//部门经理的领导是总经理//之所以在这里设置上级领导,是因为可以根据实际需求来更改设置,如果实战中上级领导人都是固定的,则可以移到领导实现类中。//提交申请groupLeader.submit(leave);}
}
二、职责链模式在源码中的运用
2.1、Spring MVC 拦截器
Spring MVC 中的拦截器也使用了责任链模式。每个拦截器可以在请求处理前后执行特定逻辑,并决定是否继续传递请求。
2.1.1、如何使用SpringMVC拦截器?
实现HandlerInterceptor接口,并重写它的三个方法:
- preHandle:在请求到达控制器之前执行。
- postHandle:在控制器处理请求之后、视图渲染之前执行。
- afterCompletion:在视图渲染之后执行(通常用于资源清理)。
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public class MyInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("MyInterceptor: preHandle - Before handling request");// 可以在这里进行权限检查、日志记录等操作return true; // 返回 true 表示继续传递请求,false 表示中断请求}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("MyInterceptor: postHandle - After handling request, before view rendering");// 可以在这里修改 ModelAndView}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("MyInterceptor: afterCompletion - After view rendering");// 可以在这里进行资源清理}
}
注册拦截器,将自定义拦截器注册到Spring MVC的配置中。可以通过实现WebMvcConfigurer接口来完成。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注册自定义拦截器,并指定拦截的路径registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**") // 拦截所有路径.excludePathPatterns("/public/**"); // 排除特定路径}
}
方便我们测试创建的contorlller。
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
public class MyController {@GetMapping("/hello")@ResponseBodypublic String hello() {System.out.println("Controller: Handling /hello request");return "Hello, World!";}
}
运行之后的结果。
MyInterceptor: preHandle - Before handling request
Controller: Handling /hello request
MyInterceptor: postHandle - After handling request, before view rendering
MyInterceptor: afterCompletion - After view rendering
2.1.2、SpringMVC拦截器是如何实现的?
在 Spring MVC 中,HandlerExecutionChain 是责任链模式的实现核心。它包含了一个处理器(Handler)和一组拦截器(Interceptors),并负责按顺序调用这些拦截器的方法。
public class HandlerExecutionChain {private final Object handler; // 处理器(通常是Controller方法)private final List<HandlerInterceptor> interceptorList = new ArrayList<>(); // 拦截器列表// 添加拦截器public void addInterceptor(HandlerInterceptor interceptor) {this.interceptorList.add(interceptor);}// 执行拦截器的 preHandle 方法boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {for (int i = 0; i < this.interceptorList.size(); i++) {HandlerInterceptor interceptor = this.interceptorList.get(i);if (!interceptor.preHandle(request, response, this.handler)) {// 如果某个拦截器返回 false,则中断请求triggerAfterCompletion(request, response, null);return false;}}return true;}// 执行拦截器的 postHandle 方法void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {for (int i = this.interceptorList.size() - 1; i >= 0; i--) {HandlerInterceptor interceptor = this.interceptorList.get(i);interceptor.postHandle(request, response, this.handler, mv);}}// 执行拦截器的 afterCompletion 方法void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex) throws Exception {for (int i = this.interceptorList.size() - 1; i >= 0; i--) {HandlerInterceptor interceptor = this.interceptorList.get(i);interceptor.afterCompletion(request, response, this.handler, ex);}}
}
DispatcherServlet 是 Spring MVC 的核心组件,它负责将请求分发给对应的处理器,并在处理过程中调用拦截器链。
public class DispatcherServlet extends FrameworkServlet {protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {// 获取处理器和拦截器链HandlerExecutionChain mappedHandler = getHandler(request);// 调用拦截器的 preHandle 方法if (!mappedHandler.applyPreHandle(request, response)) {return; // 如果某个拦截器中断请求,则直接返回}// 调用处理器(Controller方法)ModelAndView mv = handle(processedRequest, response, mappedHandler.getHandler());// 调用拦截器的 postHandle 方法mappedHandler.applyPostHandle(processedRequest, response, mv);// 渲染视图render(mv, request, response);// 调用拦截器的 afterCompletion 方法mappedHandler.triggerAfterCompletion(request, response, null);}
}
拦截器是通过 WebMvcConfigurer 接口注册的,Spring 会将这些拦截器添加到HandlerExecutionChain中。
public class InterceptorRegistration {private final HandlerInterceptor interceptor;private final List<String> pathPatterns = new ArrayList<>();public InterceptorRegistration addPathPatterns(String... patterns) {this.pathPatterns.addAll(Arrays.asList(patterns));return this;}public MappedInterceptor getMappedInterceptor() {return new MappedInterceptor(pathPatterns.toArray(new String[0]), null, interceptor);}
}
2.2、Java Web的Filter Chain
在javaWeb应用开发中,FilterChain是职责链(过滤器)模式的典型应用,以下是Filter的模拟实现分析:模拟web请求Request以及web响应Response。
public interface Request{}public interface Response{}
模拟web过滤器Filter
public interface Filter {public void doFilter(Request req,Response res,FilterChain c);}
模拟实现具体过滤器
public class FirstFilter implements Filter {@Overridepublic void doFilter(Request request, Response response, FilterChain chain) {System.out.println("过滤器1 前置处理");// 先执行所有request再倒序执行所有responsechain.doFilter(request, response);System.out.println("过滤器1 后置处理");}
}public class SecondFilter implements Filter {@Overridepublic void doFilter(Request request, Response response, FilterChain chain) {System.out.println("过滤器2 前置处理");// 先执行所有request再倒序执行所有responsechain.doFilter(request, response);System.out.println("过滤器2 后置处理");}
}
模拟实现过滤器链FilterChain
public class FilterChain {private List<Filter> filters = new ArrayList<Filter>();private int index = 0;// 链式调用public FilterChain addFilter(Filter filter) {this.filters.add(filter);return this;}public void doFilter(Request request, Response response) {if (index == filters.size()) {return;}Filter filter = filters.get(index);index++;filter.doFilter(request, response, this);}
}
测试
public class Client {public static void main(String[] args) {Request req = null;Response res = null ;FilterChain filterChain = new FilterChain();filterChain.addFilter(new FirstFilter()).addFilter(new SecondFilter());filterChain.doFilter(req,res);}
}
三、总结
职责链模式:把多个处理对象连成一条链,请求按顺序传递,谁适合处理就处理,处理不了就传给下一个。
优点:
- 解耦:发请求的不用关心谁来处理,处理者之间也不互相依赖。
- 灵活:能动态调整链条顺序,或增删处理步骤。
- 单一职责:每个处理者只做自己该做的事。
缺点:
- 可能没人处理:链条没配好时,请求可能“走完全场”也没结果。
- 性能问题:链条太长时,传递过程拖慢速度。
- 调试麻烦:请求的传递路径不直观,排查问题费劲。
适用场景
- 需要动态切换算法或行为:例如,支付方式、排序算法、资源加载策略等。
- 有多个相似的类,只有行为不同:例如,不同类型的折扣计算、不同的日志记录方式等。
- 避免使用复杂的条件语句:当代码中有大量if-else或switch-case语句时,可以用策略模式替代。
- 需要隔离算法的实现细节:当不希望暴露算法的实现细节,或者希望算法可以独立变化时。
- 需要对算法进行扩展:当系统需要支持新的算法,且不希望修改现有代码时。
参考
黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)_哔哩哔哩_bilibili
相关文章:
【设计模式】【行为型模式】职责链模式(Chain of Responsibility)
👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD 🔥 2025本人正在沉淀中… 博客更新速度 👍 欢迎点赞、收藏、关注,跟上我的更新节奏 🎵 当你的天空突…...
【H5自适应】高端科技类pbootcms网站模板 – 三级栏目、下载与招聘功能支持
(H5自适应)高端大气的科技类pbootcms网站模板 带三级栏目、下载和招聘功能 后台地址:您的域名/admin.php 后台账号:admin 后台密码:123456 为了提升系统安全,请将后台文件admin.php的文件名修改一下。修改之后,后台…...
【Java 面试 八股文】框架篇
框架篇 1. Spring框架中的单例bean是线程安全的吗?2. 什么是AOP?3. 你们项目中有没有使用到AOP?4. Spring中的事务是如何实现的?5. Spring中事务失效的场景有哪些?6. Spring的bean的生命周期?7. Spring中的…...
原型模式详解(Java)
原型模式(Prototype Pattern),作为一种极具代表性的创建型设计模式,其核心思想在于通过复制,亦即克隆现有的对象,来达成创建新对象的目的,而非依赖传统的构造函数途径。这一模式巧妙地基于现有对…...
TCP拥塞控制机制
TCP拥塞控制机制是TCP协议中至关重要的一部分,用于防止网络出现拥塞,保证网络的高效、稳定运行 拥塞控制的基本概念 拥塞:在计算机网络中,拥塞是指当网络中存在过多的分组时,网络性能下降的现象,如延迟增…...
自动化UI测试 | 什么是测试驱动开发(TDD)和行为驱动开发(BDD)?有何区别?
TDD(测试驱动开发)和BDD(行为驱动开发)是两种独特的软件开发技术,它们在测试的内容和方式上有所不同。尽管名称相似,但服务于不同的目的。 什么是TDD? TDD代表测试驱动开发。它是一个过程&…...
DeepSeek 助力 Vue 开发:打造丝滑的进度条
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 Deep…...
一场始于 Selector Error 的拯救行动:企查查数据采集故障排查记
时间轴呈现事故进程 17:00:开发人员小李正在尝试利用 Python 爬虫从企查查(https://www.qcc.com)抓取公司工商信息。原本一切正常,但突然发现信息采集失败,程序抛出大量选择器错误。17:15:小李发现&#x…...
微信服务号推送消息
这里如果 没有 就需要点新的功能去申请一下 申请成功之后就可以设置模版消息 推送到用户接受的页面是 需要后端调用接口 传递token 发送给客户...
24电子信息类研究生复试面试问题汇总 电子信息类专业知识问题最全!电子信息复试全流程攻略 电子信息考研复试真题汇总
你是不是在为电子信息考研复试焦虑?害怕被老师问到刁钻问题、担心专业面答不上来?别慌!作为复试面试92分逆袭上岸的学姐,今天手把手教你拆解电子信息类复试通关密码!看完这篇,让你面试现场直接开大…...
嵌入式EasyRTC实时通话支持海思hi3516cv610,编译器arm-v01c02-linux-musleabi-gcc
EasyRTC已经完美支持海思hi3516cv610,编译器arm-v01c02-linux-musleabi-gcc,总体SDK大小控制在680K以内(预计还能压缩100K上下): EasyRTC在hi3516cv610芯片上能双向通话、发送文字以及二进制指令,总体运行…...
如何搭建Wi-Fi CVE漏洞测试环境:详细步骤与设备配置
引言 随着Wi-Fi技术的普及,Wi-Fi网络成为了现代通信的重要组成部分。然而,Wi-Fi网络的安全性始终是一个备受关注的话题。通过漏洞扫描和安全测试,网络管理员可以及早发现并修复Wi-Fi设备中存在的安全隐患。本篇文章将详细介绍如何搭建Wi-Fi …...
sqlalchemy 使用fetchmany 报错 KeyError 或 AttributeError
问题 我遇到的问题是 AttributeError: Could not locate column in row for column xxxx 解决 首先看你定义的模型类是否缺失了相关的字段 Column XXX not found._clould not locate column in row for column-CSDN博客 其次 rows result.fetchmany(1000) for (row,) i…...
计算机视觉中图像的基础认知
一、图像/视频的基本属性 在计算机视觉中,图像和视频的本质是多维数值矩阵。图像或视频数据的一些基本属性。 宽度(W) 和 高度(H) 定义了图像的像素分辨率,单位通常是像素。例如,一张 1920x10…...
Docker Desktop WebAPI《1》
方法1 》》生成 的文档不要动, 》》执行 Container(Dockerfile) 会生成镜像文件和容器 》》生成的镜像和容器 在 Docker Desktop 中可以查看 用VS 的 Container Dockerfile 调试 但把这个调试工工具 停止,WebAPi就不能访问了 …...
ELK安装部署同步mysql数据
ELK 安装部署指南 ELK 是 Elasticsearch、Logstash 和 Kibana 的简称,用于日志收集、存储、分析和可视化。 1. 安装 Elasticsearch Elasticsearch 是一个分布式搜索和分析引擎。 1.1 下载并安装 访问 Elasticsearch 官网 下载最新版本。 解压并安装: tar…...
《OpenCV》——特征提取与匹配方法
特征提取 特征提取是从原始数据中提取出能够代表数据本质特征和关键信息的过程,在很多领域都有广泛应用。原始数据往往包含大量的冗余信息,特征提取的目的是去除这些冗余,提取出最具代表性、最能区分不同类别或模式的特征,从而降…...
Oracle DBA 诊断及统计工具-2
Oracle 数据表空间和索引表空间的资源分配比例总结 在 Oracle 数据库中,数据表空间和索引表空间并没有固定的资源分配比例,其分配需要综合考虑多种因素,以下是详细分析不同场景下的分配建议以及具体的分配思路。 影响分配比例的因素 数据读…...
如何使用DHTMLX Scheduler的拖放功能,在 JS 日程安排日历中创建一组相同的事件
DHTMLX Scheduler 是一个全面的调度解决方案,涵盖了与规划事件相关的广泛需求。假设您在我们的 Scheduler 文档中找不到任何功能,并且希望在我们的 Scheduler 文档中看到您的项目。在这种情况下,很可能可以使用自定义解决方案来实现此类功能。…...
矩阵元素的“鞍点”
题意: 一个矩阵元素的“鞍点”是指该位置上的元素值在该行上最大、在该列上最小。 本题要求编写程序,求一个给定的n阶方阵的鞍点。 输入格式: 输入第一行给出一个正整数n(1≤n≤6)。随后n行,每行给出n个整数…...
Qt的isVisible ()函数介绍和判断窗口是否在当前界面显示
1、现象:当Qt的窗口最小化时,isVisible值一定是true,这是正常的。 解释:在Qt中,当你点击窗口的最小化按钮时,Qt内部不会自动调用 hide() 方或 setVisible(false) 来隐藏窗口。相反,它会改变窗口…...
Unity-Mirror网络框架-从入门到精通之LagCompensation示例
文章目录 前言什么是滞后补偿Lag Compensation示例延迟补偿原理ServerCubeClientCubeCapture2DSnapshot3D补充LagCompensation.cs 独立算法滞后补偿器组件注意:算法最小示例前言 在现代游戏开发中,网络功能日益成为提升游戏体验的关键组成部分。本系列文章将为读者提供对Mir…...
Jenkins 通过 Execute Shell 执行 shell 脚本 七
Jenkins 通过 Execute Shell 执行 shell 脚本 七 一、创建 .sh 文件 项目目录下新建 .sh 文件 jenkins-script\shell\ci_android_master.sh添加 Execute Shell 模块 在 Command 中添加 # 获取 .sh 路径 CI_ANDROID_MASTER_PATH"${WORKSPACE}/jenkins-script/shell/…...
PyCharm 批量替换
选择替换的内容 1. 打开全局替换窗口 有两种方式可以打开全局替换窗口: 快捷键方式: 在 Windows 或 Linux 系统下,按下 Ctrl Shift R。在 Mac 系统下,按下 Command Shift R。菜单操作方式:点击菜单栏中的 Edit&…...
Linux-文件基本操作
1.基本概念 文件: 一组相关数据的集合 文件名: 01.sh //文件名 2.linux下的文件类型 b block 块设备文件 eg: 硬盘 c character 字符设备文件 eg: 鼠标,键盘 d directory 目录文件 eg: 文件夹 - regular 常规文件…...
HTTP 请求头、响应头常见字段分析
目录 请求头AcceptAccept-EncodingUser-AgentConnectionCache-ControlHost 响应头Content-EncodingETagContent-TypeVaryx-business-use-case-usageAccess-Control-Allow-Originfacebook-api-versionStrict-Transport-SecurityPragmaCache-ControlExpiresx-fb-request-id 和 x-…...
postman登录cookie设置
1.设置环境变量, 定义变量存放共享的登录信息 如Cookie 2.登录接口编码test脚本获取cookie信息 let jsessionidCookie pm.cookies.get("JSESSIONID");if (jsessionidCookie) {let cookie "JSESSIONID" jsessionidCookie "; Admin-Tok…...
Oracle临时表空间(基础操作)
临时表空间 临时表空间:用来存放用户的临时数据,临时数据在需要时被覆盖,关闭数据库后自动删除,其中不能存放永久性数据。 用户进程和服务器进程是一对一的叫做专用连接。 任何一个用户连到oracle数据库,oracle都会…...
数据结构-基础
1、概念: 程序 数据结构 算法 2、程序的好坏 可读性,稳定性,扩展性,时间复杂度,空间复杂度。 3、数据结构 是指存储、组织数据的方式,以便高效地进行访问和修改。通过选择适当的数据结构, 能…...
2024年博客之星年度评选—主题文章创作评审文章得分公布
博客之星活动地址:https://www.csdn.net/blogstar2024 创作影响力评审入围名单:https://blogdev.blog.csdn.net/article/details/145189549 目录 主题文章创作评审得分排名 主题文章创作说明 主题文章评选说明 创作影响力评审主题文章创作评审目前排名 博…...
