设计模式学习笔记-----抽象责任链模式

抽象责任链体系由 5 个关键组件构成

责任链上下文

它是责任链的 "大脑",负责处理器的注册、管理和执行调度,是整个模式的核心调度中心。

abstractChainHandlerContainer:一个Map<String, List<AbstractChainHandler>>,key 是 "责任链标识(mark)",value 是该标识下的所有处理器列表。作用是按 "业务分组" 管理处理器(比如 "用户注册" 一个组,"订单提交" 一个组)。

handler(String mark, T requestParam):对外提供的执行入口。根据传入的mark从容器中取出对应处理器列表,逐个执行处理器的handler方法(即按顺序执行链式逻辑)。若列表为空,直接抛异常(避免无处理器时的业务漏洞)。

run(String... args):实现CommandLineRunner接口(Spring 容器启动后自动执行),负责初始化处理器。从 Spring 容器中获取所有AbstractChainHandler类型的 Bean,按mark分组存入容器;同时对每个组内的处理器按getOrder()返回值排序(保证执行顺序可控)。

/*** 抽象责任链上下文*/
public final class AbstractChainContext<T> implements CommandLineRunner {private final Map<String, List<AbstractChainHandler>> abstractChainHandlerContainer = new HashMap<>();/*** 责任链组件执行** @param mark         责任链组件标识* @param requestParam 请求参数*/public void handler(String mark, T requestParam) {List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(mark);if (CollectionUtils.isEmpty(abstractChainHandlers)) {throw new RuntimeException(String.format("[%s] Chain of Responsibility ID is undefined.", mark));}abstractChainHandlers.forEach(each -> each.handler(requestParam));}@Overridepublic void run(String... args) throws Exception {Map<String, AbstractChainHandler> chainFilterMap = ApplicationContextHolder.getBeansOfType(AbstractChainHandler.class);chainFilterMap.forEach((beanName, bean) -> {List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(bean.mark());if (CollectionUtils.isEmpty(abstractChainHandlers)) {abstractChainHandlers = new ArrayList();}abstractChainHandlers.add(bean);List<AbstractChainHandler> actualAbstractChainHandlers = abstractChainHandlers.stream().sorted(Comparator.comparing(Ordered::getOrder)).collect(Collectors.toList());abstractChainHandlerContainer.put(bean.mark(), actualAbstractChainHandlers);});}
}

处理器抽象接口

它是所有具体处理器的 "规范契约",定义了处理器的核心能力。

继承Ordered接口(Spring 的排序接口),因此所有处理器必须实现getOrder()方法 —— 用于定义自身在链中的执行顺序(数字越小,优先级越高)。

handler(T requestParam):处理器的核心业务方法,实现具体的链式处理逻辑(如参数校验、数据转换等)。

mark():返回 "责任链标识",用于将处理器归到对应的业务组(比如用户注册相关的处理器,mark统一为 "USER_REGISTER_FILTER")。

/*** 抽象业务责任链组件*/
public interface AbstractChainHandler<T> extends Ordered {/*** 执行责任链逻辑** @param requestParam 责任链执行入参*/void handler(T requestParam);/*** @return 责任链组件标识*/String mark();
}

业务专属处理器接口

它是特定业务场景的处理器接口(这里是 "用户注册" 场景),继承AbstractChainHandler并固化了mark,避免具体处理器重复定义标识。

核心实现:默认重写mark()方法,返回UserChainMarkEnum.USER_REGISTER_FILTER.name()—— 即所有实现该接口的处理器,都会被自动归到 "用户注册" 这个责任链组(无需每个处理器单独写mark,减少重复代码)。

public interface UserRegisterCreateFilter<T extends AuthUserDTO> extends AbstractChainHandler<AuthUserDTO> {@Overridedefault String mark() {return UserChainMarkEnum.USER_REGISTER_FILTER.name();}}

具体处理器

它们是责任链的 "执行者",负责实现具体的业务逻辑(如参数校验、规则判断等)。以示例中的两个处理器为例:

  • UserRegisterParamNotNullChainHandler

    • 重写handler:校验AuthUserDTO中的必填参数(用户名、密码、邮箱、手机号),若有 null 则抛异常(中断链式执行)。
    • 重写getOrder():返回 0(优先级最高,需最先执行 —— 参数校验必须在业务校验前)。
  • UserRegisterHasUsernameChainHandler

    • 重写handler:调用authUserService检查用户名是否已存在,若存在则抛异常。
    • 重写getOrder():返回 1(优先级次之,参数校验通过后再检查用户名唯一性)。
@Component
public class UserRegisterHasUsernameChainHandler implements UserRegisterCreateFilter<AuthUserDTO>{@Resourceprivate AuthUserService authUserService;@Overridepublic void handler(AuthUserDTO requestParam) {if (authUserService.hasUsername(requestParam)){throw new ClientException(UserRegisterErrorCodeEnum.HAS_USERNAME_NOTNULL);}}@Overridepublic int getOrder() {return 1;}
}@Component
public final class UserRegisterParamNotNullChainHandler implements UserRegisterCreateFilter<AuthUserDTO>{@Overridepublic void handler(AuthUserDTO requestParam) {if (Objects.isNull(requestParam.getUserName())) {throw new ClientException(UserRegisterErrorCodeEnum.USER_NAME_NOTNULL);}else if (Objects.isNull(requestParam.getPassword())) {throw new ClientException(UserRegisterErrorCodeEnum.PASSWORD_NOTNULL);}else if (Objects.isNull(requestParam.getEmail())) {throw new ClientException(UserRegisterErrorCodeEnum.MAIL_NOTNULL);}else if (Objects.isNull(requestParam.getPhone())) {throw new ClientException(UserRegisterErrorCodeEnum.PHONE_NOTNULL);}}@Overridepublic int getOrder() {return 0;}
}

自动配置类

它是责任链与 Spring 容器的连接点,负责将AbstractChainContext注册为 Spring Bean,使其能被全局注入使用。

通过@Bean注解创建AbstractChainContext实例,确保上下文由 Spring 管理,从而在启动时自动执行run方法完成处理器初始化。

/*** 设计模式自动装配*/
@ImportAutoConfiguration(ApplicationBaseAutoConfiguration.class)
@Configuration
public class DesignPatternAutoConfiguration {/*** 责任链上下文*/@Beanpublic AbstractChainContext abstractChainContext() {return new AbstractChainContext();}
}

执行流程

阶段 1:Spring 启动时的处理器初始化(AbstractChainContext.run()
  1. 扫描处理器:Spring 容器启动完成后,AbstractChainContext作为CommandLineRunner,自动执行run方法。通过ApplicationContextHolder.getBeansOfType(AbstractChainHandler.class),从容器中获取所有标注了@Component的处理器(如UserRegisterParamNotNullChainHandlerUserRegisterHasUsernameChainHandler)。

  2. 按 mark 分组:遍历所有处理器,根据mark()返回值(这里两个处理器都通过UserRegisterCreateFilter默认返回 "USER_REGISTER_FILTER"),将它们归到同一组(key 为 "USER_REGISTER_FILTER",value 为包含两个处理器的列表)。

  3. 按 order 排序:对每个组内的处理器列表排序 —— 按getOrder()返回值升序排列(0 在前,1 在后),最终该组列表顺序为:UserRegisterParamNotNullChainHandler(order 0)→ UserRegisterHasUsernameChainHandler(order 1)。

  4. 存入容器:将排序后的列表存入abstractChainHandlerContainer,完成初始化。

阶段 2:业务调用时的链式执行(AbstractChainContext.handler()

当用户注册接口被调用,需要执行校验逻辑时:

  1. 调用入口:业务代码中注入AbstractChainContext,调用handler("USER_REGISTER_FILTER", authUserDTO)(传入用户注册的mark和请求参数)。

  2. 获取处理器列表:上下文从abstractChainHandlerContainer中取出 key 为 "USER_REGISTER_FILTER" 的列表(即排序后的两个处理器)。

  3. 顺序执行处理器

    • 先执行UserRegisterParamNotNullChainHandler.handler(authUserDTO):检查参数是否为空。若参数缺失(如 userName 为 null),直接抛ClientException(链式执行中断,后续处理器不执行)。
    • 若参数校验通过,再执行UserRegisterHasUsernameChainHandler.handler(authUserDTO):检查用户名是否已存在。若已存在,抛异常中断;若通过,整个链式执行完成。

总结

套抽象责任链模式,本质是通过上下文做调度接口做规范Spring 做整合,将 "多步骤业务处理" 拆分为独立的处理器,实现了 "逻辑解耦 + 顺序可控 + 灵活扩展"。尤其适合校验流程、多环节业务处理(如注册、下单、审批)等场景 —— 新增步骤只需加处理器,调整顺序只需改getOrder,极大降低了业务迭代的成本。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/2845.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【大前端】实现一个前端埋点SDK,并封装成NPM包

&#x1f680;来做一个支持 React 的前端埋点 SDK&#xff0c;并把它封装成 npm 包 的形式。整体分 3 部分&#xff1a; 核心 SDK&#xff08;独立的采集 & 上报逻辑&#xff09;React Hook / HOC 支持&#xff08;自动埋点&#xff1a;路由变化、组件渲染、点击事件&…

Leetcode+Java+dpI

509.斐波那契数斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a;F(0) 0&#xff0c;F(1) 1 F(n) F(n - 1) F(n - 2)&#xff0c;其中 n >…

IntelliJ IDEA 新手入门教程-Java、Web、Maven创建(带图解)

&#xff08;以下内容大部分来自上述课程&#xff09; ps&#xff1a;本人用的是2021.3.2 1. 下载 可以参考图文&#xff0c;但破解我没试过&#xff1a;图文参考 中文插件&#xff1a;中文插件 ps&#xff1a;中文插件的文章是博客园的&#xff0c;直接一个idea新手专栏&a…

[React]Antd Upload组件上传多个文件

前言实现需求&#xff1a;上传多个文件。其实就是获取多个文件的绝对路径交给后端接口去处理。Upload组件 首先&#xff0c;这里不能调用Upload的onChange函数&#xff0c;因为上传中、完成、失败都会调用这个函数&#xff0c;在多个文件的情况下会出现多次调用问题。改用befor…

Java-多态

多态是 Java 面向对象三大特性&#xff08;封装、继承、多态&#xff09;中最灵活也最核心的概念之一。它允许我们用统一的方式处理不同的对象&#xff0c;大幅提升代码的扩展性和复用性。本文将结合实际案例&#xff0c;从概念、实现到应用&#xff0c;全面解析 Java 多态的核…

Redis常规指令及跳表

第一部分&#xff1a;Redis 常规指令Redis 是一个键值存储系统&#xff0c;其指令通常以 COMMAND KEY_NAME [ARGUMENTS...] 的形式存在。下面我们按照数据结构和功能来分类。1. 全局/键操作指令这些指令不特定于某一数据类型&#xff0c;适用于所有键。指令描述示例KEYS patter…

指纹云手机×Snapchat Spotlight:动态GPS+陀螺仪仿生方案

——基于时空坐标系重构与生物运动模拟的AR营销突破​​一、Snapchat Spotlight广告的技术困局​设备指纹关联风险​Snapchat通过陀螺仪基线值&#xff08;0.1误差&#xff09;和GPS坐标&#xff08;精度&#xff1c;5米&#xff09;构建设备指纹&#xff0c;相似度&#xff1e…

Java 编辑器与 IDE:开发者手中的利剑与盾牌

&#x1f525;个人主页&#xff1a;艾莉丝努力练剑 ❄专栏传送门&#xff1a;《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题、洛谷刷题、C/C基础知识知识强化补充、C/C干货分享&学习过程记录 &#x1f349;学习方向&#xff1a;C/C方向学习者…

不再让Windows更新!Edge游戏助手卸载及关闭自动更新

文章目录Windows系统更新问题方法一&#xff1a;通过注册表手动设置1. 打开注册表编辑器2. 定位到目标路径3. 创建新的DWORD值4. 修改数值方法二&#xff1a;命令行设置1. 打开命令提示符2. 输入命令验证设置是否生效恢复更新Edge关闭游戏助手Edge关闭后台运行Edge关闭自动更新…

从Android到鸿蒙:一场本应无缝的转型-优雅草卓伊凡

从Android到鸿蒙&#xff1a;一场本应无缝的转型-优雅草卓伊凡看到Android开发者询问如何转向鸿蒙&#xff0c;卓伊凡不禁摇头&#xff1a;真正的Android工程师根本不需要“学习”鸿蒙&#xff0c;只需要简单查阅文档即可。近年来&#xff0c;随着鸿蒙系统的不断发展&#xff0…

Linux的线程概念与控制

目录 1、Linux的线程概念 1.1 什么是线程 1.2 分页式存储管理 1.3 线程的优点 1.4 线程的缺点 3、Linux的线程控制 3.1 POSIX线程库 3.2 线程创建 3.3 线程退出 3.4 线程等待 3.5 线程分离 1、Linux的线程概念 1.1 什么是线程 首先Linux内核不区分"进程"…

云原生俱乐部-RH294知识点归纳(3)

其实ansible还剩下使用角色和ansible内容集合来简化playbook、对ansible进行故障排除和自动执行Linux管理任务三部分。至于如何对ansible进行故障排除&#xff0c;只有在生产中碰到了故障才用得上&#xff0c;并且即使碰上的还是需要具体问题具体分析&#xff0c;但是可以该部分…