09.责任链模式
09. 责任链模式
什么是责任链设计模式?
责任链设计模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许将请求沿着处理者对象组成的链进行传递,直到有一个处理者对象能够处理该请求为止。这种模式的目的是解耦请求的发送者和接收者,使得多个对象都有机会处理请求,从而增强了系统的灵活性。
责任链模式通常包含以下几个角色:
- 请求者(Client):发起请求的对象。
- 抽象处理者(Handler):定义一个处理请求的接口,通常包含一个方法用于处理请求,以及一个指向下一个处理者的引用。
- 具体处理者(Concrete Handler):实现抽象处理者接口的具体类,负责处理它所负责的请求,并决定是否将请求传递给链中的下一个处理者。
- 链(Chain):包含多个处理者对象,负责将请求沿着链传递。
责任链模式的工作原理如下:
-
请求者创建一个请求并将其发送给链的起始处理者。
-
每个处理者对象检查请求是否由自己处理。
-
- 如果能够处理,则处理请求并结束责任链。
- 如果不能处理,则将请求传递给链中的下一个处理者。
-
这个过程一直持续,直到请求被处理或传递到链的末端。
责任链模式的优点包括:
- 增强了系统的灵活性和可扩展性,因为可以动态地添加或移除处理者。
- 降低了对象之间的耦合度,因为发送者和接收者不需要直接交互。
- 允许多个对象处理同一个请求,增加了处理请求的灵活性。
责任链模式的缺点包括:
- 请求的传递路径可能难以跟踪,尤其是在链很长或处理者逻辑复杂的情况下。
- 责任链可能会导致系统性能问题,因为请求需要在多个对象之间传递。
责任链模式在实际应用中非常广泛,例如在GUI编程中处理事件、在网络编程中处理请求、在工作流系统中处理任务等场景。
举个简单的需求:
假如我们有个登录的场景,在登录处理流程中,需要校验参数、填充参数、登录判断、登录日志记录。我们每步都是环环相扣,此时就可以使用责任链模式。
有几个重要角色:
- **Action:**责任链中的一个执行动作,主要定义具体执行动作,以及是否需要跳过。
- **ActionChain:**责任链,用于定义添加执行动作方法,以及调度整条链路动作执行。
- **ActionContext:**执行动作上下文,定义一个上下文对象,用来在链路执行过程中存储和传输数据。
将上面的步骤看做一个一个执行动作,建立对应的action,使用 chain 将多个 action 进行串联,同时我们可以定义一个context 上下文,用来在各个action之间传输数据。
除此之外,我们也可以通过配置中心,来定义哪些步骤需要执行,哪些可以跳过。
类图如下:

代码编写:
1、定义顶级接口
(1)定义责任链执行动作上下文抽象类,用于责任链上下文之间数据传输。
@Data
public abstract class ActionContext implements Serializable, Cloneable {private static final long serialVersionUID = 1L;/*** 执行链名称,用于获取配置*/private String actionChainName;/*** 跳到结果处理*/private boolean isSkipToResult = false;public Object clone() throws CloneNotSupportedException {return super.clone();}
}
(2)定义责任链执行动作基类
public interface IAction<T extends ActionContext> {/*** 是否需要跳过* @param context 上下文* @return true/false*/default boolean isSkippered(T context) throws Exception{if (context.isSkipToResult()) {return true;}// 通过配置中心获取是否需要执行List<String> config = ConfigServer.getConfig(context.getActionChainName());if (config.contains(getName())) {return false;}return true;}/*** 执行* @param context 上下文*/void execute(T context) throws Exception;/*** 获取执行动作名称,用于和配置中心进行匹配* @return*/String getName();}
(3)定义Action 执行链接口
public interface IActionChain<T extends ActionContext> {/*** 添加一个Action* @param action 上下文* @return action链*/IActionChain<T> appendAction(Class<? extends IAction<T>> action);IActionChain<T> appendActions(List<Class<? extends IAction<T>>> actions);IActionChain<T> appendAction(IAction<T> action);/*** 执行动作* @param context 上下文*/void execute(T context) throws Exception;
}
2、实现接口,定义具体的执行
(1)登录上下文
/*** 登录上下文*/
@EqualsAndHashCode(callSuper = true)
@Data
public class LoginActionContext extends ActionContext {/*** 失败日志*/private String failMsg;private String userName;private String password;private String token;private String ip;private String device;private Boolean isLoginFlag = true;
}
(2)定义责任链通用模版类
public class RouteActionChain<T extends ActionContext> implements IActionChain<T> {private List<IAction<T>> actionChain = new ArrayList<IAction<T>>();@Overridepublic IActionChain<T> appendAction(Class<? extends IAction<T>> action) {actionChain.add(getActionInstance(action));return this;}@Overridepublic IActionChain<T> appendActions(List<Class<? extends IAction<T>>> actions) {if (CollectionUtils.isEmpty(actions)) {return this;}for (Class<? extends IAction<T>> clazz : actions) {actionChain.add(getActionInstance(clazz));}return this;}@Overridepublic IActionChain<T> appendAction(IAction<T> action) {actionChain.add(action);return this;}@Overridepublic void execute(T context) throws Exception {for (IAction<T> action : actionChain) {//如果跳过 就不需要继续执行,这里顺序不能改变if (action.isSkippered(context)) {continue;}action.execute(context);}}public static <T extends ActionContext> IAction<T> getActionInstance(Class<? extends IAction<T>> clazz) {Collection<? extends IAction<T>> s = BeanUtil.getBeans(clazz);if (s != null && s.size() == 1) {return s.iterator().next();} else {throw new RuntimeException("action is not found");}}
}
(3)定义执行动作
- 校验参数执行动作
@Slf4j@Componentpublic class CheckParamAction implements IAction<LoginActionContext> {@Overridepublic void execute(LoginActionContext context) {// do somethinglog.info("CheckParamAction execute......");// 使用断言,判断用户名不为空try {Assert.isTrue(!StringUtils.isEmpty(context.getUserName()), "用户名不能为空");Assert.isTrue(!StringUtils.isEmpty(context.getPassword()), "密码不能为空");} catch (Exception e) {context.setIsLoginFlag(false);context.setSkipToResult(true);context.setFailMsg(e.getMessage());}}@Overridepublic String getName() {return "CheckParamAction";}}
填充参数执行动作
@Slf4j@Componentpublic class FullParamAction implements IAction<LoginActionContext> {@Autowiredprivate ConfigServer configServer;@Overridepublic void execute(LoginActionContext context) throws Exception {log.info("FullParamAction execute......");// 使用断言,判断用户名不为空try {// do somethingcontext.setIp("127.0.0.1");context.setDevice("PC");context.setToken("123456");} catch (Exception e) {context.setIsLoginFlag(false);context.setSkipToResult(true);context.setFailMsg(e.getMessage());}}@Overridepublic String getName() {return "FullParamAction";}}
- 登录判断执行动作
@Slf4j@Componentpublic class LoginAction implements IAction<LoginActionContext> {@Overridepublic void execute(LoginActionContext context) throws Exception {// 模拟登录log.info("LoginAction execute......");try {// do somethingif(context.getUserName().equals(context.getPassword())) {context.setIsLoginFlag(true);} else {context.setIsLoginFlag(false);context.setFailMsg("用户名或密码输入错误");}} catch (Exception e) {context.setIsLoginFlag(false);context.setSkipToResult(true);context.setFailMsg(e.getMessage());}}@Overridepublic String getName() {return "LoginAction";}}
- 登录日志记录执行动作
@Slf4j
@Component
public class LogAction implements IAction<LoginActionContext> {@Overridepublic void execute(LoginActionContext context) throws Exception {log.info("FullParamAction execute......");// 使用断言,判断用户名不为空try {// do somethinglog.info("数据库插入登录日志:{}", JSONObject.toJSONString(context));} catch (Exception e) {context.setIsLoginFlag(false);context.setSkipToResult(true);context.setFailMsg(e.getMessage());}}@Overridepublic String getName() {return "LogAction";}
}
(4)模拟配置中心
配置需要的执行动作,没有配置的自动跳过
@Component
@Data
public class ConfigServer {private static Map<String, List<String>> configMap = new HashMap<>();@PostConstructpublic void init(){ArrayList<String> configList = new ArrayList<>();configList.add("CheckParamAction");configList.add("FullParamAction");configList.add("LogAction");configList.add("LoginAction");configMap.put("login", configList);}/*** 获取配置列表* @param actionChainName* @return*/public static List<String> getConfig(String actionChainName) {return configMap.getOrDefault(actionChainName, new ArrayList<>());}
}
3、测试
定义测试接口
@Service
public class LoginServiceImpl implements LoginService {@Overridepublic boolean login(String userName, String password) {// 创建上下文LoginActionContext loginActionContext = new LoginActionContext();loginActionContext.setActionChainName("login");loginActionContext.setUserName(userName);loginActionContext.setPassword(password);// 构建责任链RouteActionChain<LoginActionContext> chain = new RouteActionChain<>();chain.appendAction(CheckParamAction.class);chain.appendAction(FullParamAction.class);chain.appendAction(LoginAction.class);chain.appendAction(LogAction.class);try {chain.execute(loginActionContext);} catch (Exception e) {throw new RuntimeException(e);}return loginActionContext.getIsLoginFlag();}
}
(1)测试正常情况,传输正确的 username 和 password。

所有执行动作正常执行。
(2)测试异常情况, 传输错误的 username 和 password。

中间 LoginAction 执行失败,自动跳出责任链,后续执行动作未执行。
到此,一个简单的责任链设计模式的 demo 就已完成。
拓展点:
• 可以对接配置中心,动态定义不同业务逻辑中需要执行的动作。
• 可以将幂等性校验,添加到判断动作是否执行逻辑中。
相关文章:
09.责任链模式
09. 责任链模式 什么是责任链设计模式? 责任链设计模式(Chain of Responsibility Pattern)是一种行为设计模式,它允许将请求沿着处理者对象组成的链进行传递,直到有一个处理者对象能够处理该请求为止。这种模式的目的…...
Amazon云计算AWS(一)
目录 一、基础存储架构Dynamo(一)Dynamo概况(二)Dynamo架构的主要技术 二、弹性计算云EC2(一)EC2的基本架构(二)EC2的关键技术(三)EC2的安全及容错机制 提供的…...
十_信号4-SIGCHLD信号
SIGCHLD信号 在学习进程控制的时候,使用wait和waitpid系统调用何以回收僵尸进程,父进程可以阻塞等待,也可以非阻塞等待,采用轮询的方式不停查询子进程是否退出。 采用阻塞式等待,父进程就被阻塞了,什么都干…...
HCIP的学习(27)
RSTP—802.1W—快速生成树协议 STP缺陷: 1、收敛速度慢----STP的算法是一种被动的算法,依赖于计时器来进行状态变化 2、链路利用率低 RSTP向下兼容STP协议。(STP不兼容RSTP) 改进点1—端口角色 802.1D协议---根端口、指定端口…...
6. MySQL 查询、去重、别名
文章目录 【 1. 数据表查询 SELECT 】1.1 查询表中所有字段使用 * 查询表的所有字段列出表的所有字段 1.2 查询表中指定的字段 【 2. 去重 DISTINCT 】【 3. 设置别名 AS 】3.1 为表指定别名3.2 为字段指定别名 【 5. 限制查询结果的条数 LIMIT 】5.1 指定初始位置5.2 不指定初…...
Oracle导出clob字段到csv
使用UTL_FILE ref: How to Export The Table with a CLOB Column Into a CSV File using UTL_FILE ?(Doc ID 1967617.1) --preapre data CREATE TABLE TESTCLOB(ID NUMBER, MYCLOB1 CLOB, MYCLOB2 CLOB ); INSERT INTO TESTCLOB(ID,MYCLOB1,MYCLOB2) VALUES(1,Sample row 11…...
C++无锁(lock free)队列moodycamel::ConcurrentQueue
moodycamel::ConcurrentQueue介绍 moodycamel::ConcurrentQueue一个用C++11实现的多生产者、多消费者无锁队列。 它具有以下特点: 1.快的让人大吃一惊,详见不同无锁队列之间的压测对比 2.单头文件实现,很容易集成到你的项目中 3.完全线程安全的无锁队列,支持任意线程数的并…...
python办公自动化——(二)替换PPT文档中图形数据-柱图
效果: 数据替换前 : 替换数据后: 实现代码 import collections.abc from pptx import Presentation from pptx.util import Cm,Pt import pyodbc import pandas as pd from pptx.chart.data impo…...
vue不同页面切换的方式(Vue动态组件)
v-if实现 <!--Calender.vue--> <template><a-calendar v-model:value"value" panelChange"onPanelChange" /></template> <script setup> import { ref } from vue; const value ref(); const onPanelChange (value, mod…...
Linux下Qt Creator无法输入中文(已解决)
1. 首先确保安装了搜狗输入法,且能正常运行。 2.克隆源码到本地。 git clone https://gitcode.com/fcitx/fcitx-qt5.git 3.检查Qt Creator版本,如下图所示,为基于Qt6的。 4. 进入源码目录,建立build文件夹,修改CMak…...
Codeforces 提交Java代码(自己处理输入输出)
示例一(A. Watermelon) 题目地址 Problem - 4A - Codeforces 题目截图 提交方式 可以提交本地文件,也可以在线提交。我们这里选择在线提交方式,点击上图中的 SUBMIT 按钮,会进入如下界面。 输入Java代码效果如下&a…...
剖析vue中nextTick源码
代码逻辑梳理: callbacks 数组用于存储待执行的回调函数,waiting 变量用于标记是否有待执行的回调函数。 flushCallbacks 函数用于执行所有存储在 callbacks 数组中的回调函数,并在执行完成后将 waiting 设置为 false。 timer 函数根据环境…...
SSM牙科诊所管理系统-计算机毕业设计源码98077
目 录 摘要 1 绪论 1.1研究目的与意义 1.2国内外研究现状 1.3ssm框架介绍 1.4论文结构与章节安排 2 牙科诊所管理系统系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能…...
【C++进阶】深入STL之string:模拟实现走进C++字符串的世界
📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C “ 登神长阶 ” 🤡往期回顾🤡:C模板入门 🌹🌹期待您的关注 🌹🌹 ❀STL之string 📒1. string…...
go语言linux安装
下载:https://go.dev/dl/ 命令行使用 wget https://dl.google.com/go/go1.19.3.linux-amd64.tar.gz解压下载的压缩包,linux建议放在/opt目录下 我放在/home/ihan/go_sdk下 sudo tar -C /home/ihan/go_sdk -xzf go1.19.3.linux-amd64.tar.gz 这里的参数…...
vi和vim有什么不同?
vi 和 vim 都是流行的文本编辑器,它们之间有以下主要区别: 历史: vi 是一个非常古老的文本编辑器,最初由 Bill Joy 在 1976 年为 Unix 系统编写。vim(Vi IMproved)是 vi 的一个增强版,由 Bram M…...
CSS动画效果(鼠标滑过按钮动画)
1.整体效果 https://mmbiz.qpic.cn/sz_mmbiz_gif/EGZdlrTDJa5SXiaicFfsrcric7TJmGO6YddqC4wFPdM7PGzPHuFgvtDS7MIvnLHB4WFaKia0Qh8VCyUaoyHMc2Zltg/640?wx_fmtgif&fromappmsg&tpwebp&wxfrom5&wx_lazy1&wx_co1 网页设计中的按钮不仅是用户交互的桥梁&#…...
数据结构(C):从初识堆到堆排序的实现
目录 🌞0.前言 🚈 1.堆的概念 🚈 2.堆的实现 🚝2.1堆向下调整算法 🚝2.2堆的创建(堆向下调整算法) ✈️2.2.1 向下调整建堆时间复杂度 🚝2.3堆向上调整算法 🚝2.…...
ChatGLM3-6B部署
ZhipuAI/chatglm3-6b 模型文件地址 chatglm3-6B-32k-int4 量化的模型地址 ChatGLM3 代码仓库 ChatGLM3 技术文档 cpolar http xxx 端口 /anaconda3/envs/chatglm2/lib/python3.8/site-packages/gradio$ networking.py 硬件环境 最低要求: 为…...
代码随想录35期Day54-JavaScript
Day54题目 ### LeetCode739每日温度 核心思想:今天主要是学会单调栈的使用.找到比元素更大的下一个元素,如果比栈顶元素小就入栈,否则就出栈顶元素,当前元素就是比栈顶元素大的"下一个更大的元素". /*** param {number[]} temperatures* return {number[]}*/ var …...
龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
作为测试我们应该关注redis哪些方面
1、功能测试 数据结构操作:验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化:测试aof和aof持久化机制,确保数据在开启后正确恢复。 事务:检查事务的原子性和回滚机制。 发布订阅:确保消息正确传递。 2、性…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...
Linux安全加固:从攻防视角构建系统免疫
Linux安全加固:从攻防视角构建系统免疫 构建坚不可摧的数字堡垒 引言:攻防对抗的新纪元 在日益复杂的网络威胁环境中,Linux系统安全已从被动防御转向主动免疫。2023年全球网络安全报告显示,高级持续性威胁(APT)攻击同比增长65%,平均入侵停留时间缩短至48小时。本章将从…...
TJCTF 2025
还以为是天津的。这个比较容易,虽然绕了点弯,可还是把CP AK了,不过我会的别人也会,还是没啥名次。记录一下吧。 Crypto bacon-bits with open(flag.txt) as f: flag f.read().strip() with open(text.txt) as t: text t.read…...
