当前位置: 首页 > news >正文

设计模式之责任链讲解

责任链模式适用于需要将请求和处理解耦的场景,同时又需要动态地组织处理逻辑的场景。
通过使用责任链模式,可以实现请求的动态处理、灵活的扩展和简化的代码编写,提高系统的可维护性和可扩展性。

一、责任链入门

以下这是GPT生成的责任链代码:

1.定义抽象处理器接口

// 定义抽象处理者接口
interface Handler {void handleRequest(int request);void setNextHandler(Handler handler);
}

2. 具体处理者类1

class ConcreteHandler1 implements Handler {// 定义第二个处理器private Handler nextHandler;@Overridepublic void handleRequest(int request) {if (request >= 0 && request < 10) {System.out.println("ConcreteHandler1 处理请求:" + request);} else if (nextHandler != null) {nextHandler.handleRequest(request);}}@Overridepublic void setNextHandler(Handler handler) {this.nextHandler = handler;}
}

3. 具体处理者类2

class ConcreteHandler2 implements Handler {private Handler nextHandler;@Overridepublic void handleRequest(int request) {if (request >= 10 && request < 20) {System.out.println("ConcreteHandler2 处理请求:" + request);} else if (nextHandler != null) {nextHandler.handleRequest(request);}}@Overridepublic void setNextHandler(Handler handler) {this.nextHandler = handler;}
}

4. 客户端类

public class Client {public static void main(String[] args) {// 创建具体处理者对象Handler handler1 = new ConcreteHandler1();Handler handler2 = new ConcreteHandler2();// 设置处理者之间的关系handler1.setNextHandler(handler2);// 创建请求并发送给责任链的第一个处理者handler1.handleRequest(5);handler1.handleRequest(15);handler1.handleRequest(25);}
}

在这个示例中,Handler 是抽象处理者接口,定义了处理请求的方法 handleRequest 和设置下一个处理者的方法 setNextHandler。ConcreteHandler1 和 ConcreteHandler2 是具体处理者类,分别处理不同范围的请求。Client 是客户端类,创建具体处理者对象并设置它们之间的关系,然后发送请求给责任链的第一个处理者。当请求发送到责任链后,责任链中的处理者按顺序尝试处理请求,直到有一个处理者处理了请求为止。
handler1.handleRequest(15); 表示将请求值为15的请求发送给责任链的第一个处理者 handler1。在当前设计的责任链模式中,一旦有一个处理者处理了请求,处理链就会终止,不再将请求传递给下一个处理者。因此,每次发送请求后,只有第一个能够处理该请求的处理者会对其进行处理。因为具体的实现类中是通过if...else进行处理器的整合的,当然也可以修改为只要满足特定的处理条件,处理器就会继续往下走。

责任链作为一种设计模式,目的是为了让代码更加优雅,复用性更高。

二、上下文责任链

1. 定义订单处理器模板接口

public interface OrderProcessor {// 自定义void processOrder(Order order, OrderContext context);// 公共方法void preRequestHandler(Order order);
}

2. 定义订单处理器抽象类模板

实现公共方法

public abstract class AbstractOrderProcessor implements OrderProcessor {@Overridepublic void preRequestHandler(Order order) {System.out.println("采购员对订单统一处理");}
}

3. 订单处理器实现

3.1 开始处理器
@Component
@HandlerOrder(order = 1)
public class PendingOrderProcessor extends AbstractOrderProcessor {@Overridepublic void processOrder(Order order, OrderContext context) {// 处理待处理的订单逻辑System.out.println("开始订单处理: " + order.getId());// 可以通过上下文对象访问共享信息System.out.println("Context: " + context.getInfo());}
}
3.2 批准处理器
@Component
@HandlerOrder(order = 2)
public class ApprovedOrderProcessor extends AbstractOrderProcessor{@Overridepublic void processOrder(Order order, OrderContext context) {// 处理已批准的订单逻辑System.out.println("批准订单: " + order.getId());// 可以通过上下文对象访问共享信息System.out.println("Context: " + context.getInfo());}
}
3.3 发货处理器
@Component
@HandlerOrder(order = 3)
public class ShippedOrderProcessor extends AbstractOrderProcessor{@Overridepublic void processOrder(Order order, OrderContext context) {// 处理已发货的订单逻辑System.out.println("订单发货: " + order.getId());// 可以通过上下文对象访问共享信息System.out.println("Context: " + context.getInfo());}
}

4. 订单责任链

@Component
public class OrderProcessorChain {private final ApplicationContext applicationContext;Collection<OrderProcessor> processors = new ArrayList<>();// 注入 ApplicationContextpublic OrderProcessorChain(ApplicationContext applicationContext) {this.applicationContext = applicationContext;// 获取所有订单处理器this.processors = applicationContext.getBeansOfType(OrderProcessor.class).values();}// 根据注解值进行排序protected List<OrderProcessor> getProcessors() {return this.processors.stream().sorted((o1, o2) -> {HandlerOrder order1 = o1.getClass().getAnnotation(HandlerOrder.class);HandlerOrder order2 = o2.getClass().getAnnotation(HandlerOrder.class);if (order1 == null && order2 == null) {return 0; // 如果两者都没有注解,则认为它们相等} else if (order1 == null) {return 1; // 如果 o1 没有注解但 o2 有注解,则 o1 排在 o2 前面} else if (order2 == null) {return -1; // 如果 o1 有注解但 o2 没有注解,则 o1 排在 o2 后面} else {return Integer.compare(order1.order(), order2.order()); // 比较两个注解的 order 属性}}).collect(Collectors.toList());}public void process(Order order, OrderContext context) {List<OrderProcessor> processors = getProcessors();// 依次调用订单处理器处理订单for (OrderProcessor processor : processors) {processor.preRequestHandler(order);processor.processOrder(order, context);}}
}

5. 实体类和注解

@Data
public class Order { private int id;
}@Data
public class OrderContext {private String info; // 上下文信息
}// 指定注解的保留策略
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlerOrder {int order();
}

6. 测试

@SpringBootTest
public class OrderProcessorChainTest {@Autowiredprivate OrderProcessorChain processorChain;@Testpublic void testOrderProcessing() {// 创建订单Order order = new Order();order.setId(123);// 创建订单上下文对象OrderContext context = new OrderContext();context.setInfo("Some info for processing orders");// 处理订单processorChain.process(order, context);}
}

结果:

采购员对订单统一处理
开始订单处理: 123
Context: Some info for processing orders
采购员对订单统一处理
批准订单: 123
Context: Some info for processing orders
采购员对订单统一处理
订单发货: 123
Context: Some info for processing orders

三、Spring过滤器链

责任链在Spring中怎么运用呢?
在Spring框架中,责任链模式常常运用在拦截器(Interceptor)和过滤器(Filter)等场景中,用于处理HTTP请求、消息传递等。

拦截器(Interceptor):在Spring MVC中,拦截器用于在处理请求之前或之后执行一些操作,比如权限检查、日志记录等。你可以定义多个拦截器,它们按照顺序构成一个责任链。当一个请求到达时,会依次执行每个拦截器的预处理方法和后处理方法,类似于责任链模式的行为。

过滤器(Filter):在Spring框架中,你也可以使用Servlet过滤器来对HTTP请求进行预处理或后处理。过滤器链在Servlet容器中会按照配置顺序依次执行,也可以看作是一个责任链模式的应用。

事件监听器(Event Listener):Spring框架提供了事件监听器机制,你可以定义自己的事件和监听器,并通过Spring容器进行管理。当某个事件发生时,监听器会按照注册顺序依次被调用,类似于责任链模式。

下面是一个简单的Spring拦截器示例:

1. 定义拦截器

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 在请求处理之前执行的操作,比如权限检查,未登录则直接拦截if (!checkAuth(request)) {response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);return false;}// 放行return true;}// 可以不重写该方法@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {// 在请求处理之后执行的操作}// 可以不重写该方法@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 在请求完成之后执行的操作}private boolean checkAuth(HttpServletRequest request) {// 检查用户是否有权限// 这里假设简单地检查请求中是否包含合法的身份验证信息String authToken = request.getHeader("Authorization");return authToken != null && authToken.equals("valid_token");}
}

在这个示例中, LoginInterceptor 类实现了 HandlerInterceptor 接口,它是Spring MVC中的拦截器。preHandle 方法用于在请求处理之前执行操作,比如权限检查;postHandle 和 afterCompletion 方法分别用于在请求处理之后执行操作。多个拦截器可以组成一个责任链,按照它们的注册顺序依次执行。
代码中只是提供了拦截器的实现,但是并没有形成责任链,那么多个拦截器是如何形成链路并进行请求的处理的呢?

2. 过滤器链配置

@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Resourceprivate EmployeeService employeeService;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 登录拦截器registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns("/user/login","/employee/login").order(2);// 管理员拦截器,用户可操作的功能都排除registry.addInterceptor(new AdminLoginInterceptor(employeeService)).excludePathPatterns("/user/login","/user/logout").order(3);// token刷新的拦截器,order值越小,越先执行,这个拦截器对所有请求都执行registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(1);}}

在上面的代码中MvcConfig 实现了 WebMvcConfigurer 类,并且通过addInterceptors方法项过滤器链中增加了两个拦截器LoginInterceptorAdminLoginInterceptor。这两个拦截器通过order指定了拦截器的顺序。两个拦截器在请求过来时都会触发,拦截器会根据自身配置的拦截路径做过滤处理。
我们可以来看下 WebMvcConfigurer接口的定义:接口定义了很多方法,其中有一个addInterceptors方法就是将拦截器添加到链路中的。这个接口其实有点像我们在责任链入门中讲到的定义抽象处理器接口中的setNextHandler,通过registry.addInterceptor()来将处理器进行组织。

public interface WebMvcConfigurer {... default void addInterceptors(InterceptorRegistry registry) {}
}

InterceptorRegistry类中会通过addInterceptor方法将处理器添加到集合中去。

public class InterceptorRegistry {private final List<InterceptorRegistration> registrations = new ArrayList();private static final Comparator<Object> INTERCEPTOR_ORDER_COMPARATOR;public InterceptorRegistry() {}public InterceptorRegistration addInterceptor(HandlerInterceptor interceptor) {InterceptorRegistration registration = new InterceptorRegistration(interceptor);this.registrations.add(registration);return registration;}
}

通过addInterceptors方法将拦截器添加到Spring MVC的拦截器链中。当Spring MVC接收到一个请求时,它会通过DispatcherServlet进行处理。DispatcherServlet会调用拦截器链中的每个拦截器来处理请求。

具体来说,以下是请求如何经过拦截器链被拦截器处理的过程:

当一个请求到达DispatcherServlet时,DispatcherServlet会创建一个用于处理该请求的HandlerExecutionChain。

在创建HandlerExecutionChain的过程中,DispatcherServlet会检查是否存在拦截器链。如果存在,DispatcherServlet会将该请求与拦截器链关联起来。

在处理请求之前,DispatcherServlet会依次调用拦截器链中每个拦截器的preHandle方法。拦截器的preHandle方法可以用来进行预处理,比如身份验证、日志记录等操作。

如果拦截器链中的所有拦截器的preHandle方法都返回true,表示请求可以继续处理。此时,DispatcherServlet会调用与请求匹配的处理器(Controller)来处理请求。

处理器处理完请求后,DispatcherServlet会依次调用拦截器链中每个拦截器的postHandle方法。拦截器的postHandle方法可以用来进行后处理,比如修改响应内容等操作。

最后,DispatcherServlet会依次调用拦截器链中每个拦截器的afterCompletion方法。拦截器的afterCompletion方法会在请求完成后被调用,无论请求是否成功处理。

通过这个过程,拦截器能够在请求处理的不同阶段进行干预,实现诸如权限验证、日志记录、异常处理等功能。拦截器链的顺序由拦截器的order方法决定,数值越小的拦截器优先级越高。

四、总结

关于责任链模式,必须要有处理器模板,要有链,链可以是集合,也可以是类似链表。请求就会沿着这条链依次被链上处理器处理。

相关文章:

设计模式之责任链讲解

责任链模式适用于需要将请求和处理解耦的场景&#xff0c;同时又需要动态地组织处理逻辑的场景。 通过使用责任链模式&#xff0c;可以实现请求的动态处理、灵活的扩展和简化的代码编写&#xff0c;提高系统的可维护性和可扩展性。 一、责任链入门 以下这是GPT生成的责任链代…...

K8s: 将一个节点移出集群和相关注意事项

前置步骤 在Kubernetes集群中&#xff0c;要移出一个节点&#xff0c;你需要执行以下步骤&#xff1a; 1 &#xff09;将节点标记为不可调度 首先&#xff0c;你需要将目标节点标记为不可调度&#xff0c;以确保Kubernetes不会在该节点上调度新的Pod这可以通过执行以下命令实…...

Python学习笔记24 - 学生信息管理系统

1. 需求分析 2. 系统设计 3. 系统开发必备 4. 主函数设计 5. 学生信息维护模块设计 a. 录入学生信息 b. 删除学生信息 c. 修改学生信息 d. 查询学生信息 e. 统计学生总人数 f. 显示所有学生信息 g. 排序模块设计 6. 项目打包...

【物联网应用案例】某制造企业电锅炉检测项目

供暖行业在我国的经济发展中占据着重要的地位&#xff0c;然而&#xff0c;长期以来&#xff0c;该行业存在着自动化水平低、管理效率不高等问题&#xff0c;制约了其持续发展。为了解决这些问题&#xff0c;吉林某电锅炉生产厂家进行了一项创新性的尝试。 该厂家通过集成物联…...

设计模式实践

结合设计模式概念和在java/spring/spring boot中的实战&#xff0c;说明下列设计模式。 一、工厂模式 这里只讲简单工厂模式&#xff0c;详细的可以参考Java工厂模式&#xff08;随笔&#xff09;-CSDN博客。工厂类会根据不同的参数或条件来决定创建哪种对象&#xff0c;这样…...

嵌入式学习52-ARM1

知识零散&#xff1a; 1.flash&#xff1a; nor flash 可被寻地址 …...

Java(MySQL基础)

数据库相关概念 MySOL数据库 关系型数据库(RDBMS) 概念: 建立在关系模型基础上&#xff0c;由多张相互连接的二维表组成的数据库。特点: 使用表存储数据&#xff0c;格式统一&#xff0c;便于维护使用SQL语言操作&#xff0c;标准统一&#xff0c;使用方便 SQL SOL通用语法…...

预约系统的使用

预约系统的使用 目录概述需求&#xff1a; 设计思路实现思路分析1.用户年规则 在 预约系统中的使用流程 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wa…...

酷开科技OTT大屏营销:开启新时代的营销革命

随着互联网技术的不断发展和普及&#xff0c;大屏已经成为越来越多家庭选择的娱乐方式。在这个背景下&#xff0c;酷开科技凭借其强大的技术实力和敏锐的市场洞察力&#xff0c;成功地将大屏转化为一种新的营销渠道&#xff0c;为品牌和企业带来了前所未有的商业机会。 酷开科技…...

网络安全(防火墙,IDS,IPS概述)

问题一:什么是防火墙,IDS,IPS? 防火墙是对IP:port的访问进行限制,对访问端口进行制定的策略去允许开放的访问,将不放开的端口进行拒绝访问,从而达到充当防DDOS的设备。主要是拒绝网络流量,阻断所有不希望出现的流程,禁止数据流量流通,达到安全防护的作用。如将一些恶…...

安装IntelliJ IDEA插件教程

安装IntelliJ IDEA插件&#xff1a;一份详细指南 在提升IntelliJ IDEA开发效率的过程中&#xff0c;插件扮演着不可或缺的角色。它们为IDE提供了额外的功能和工具&#xff0c;以满足开发者在特定编程语言、框架、测试、版本控制等方面的个性化需求。本文将为您详细阐述如何在I…...

大厂基础面试题(之四)

Q1&#xff1a;请先进行自我介绍 Q2&#xff1a;说下你学习前端的详细过程 Q3&#xff1a;一个页面从url输入到显示页面的整个过程 1.URL解析 2.DNS解析 3.建立TCP连接 4.发起HTTP请求 5.服务器处理请求 6.返回HTTP响应 7.下载页面资源 8.解析和渲染页面 9.JavaScript执行 10…...

为什么我们应该切换到Rust

What is RUST? 什么是Rust&#xff1f; Rust is a programming language focused on safety, particularly safe concurrency, supporting functional and imperative-procedural paradigms. Rust is syntactically similar to C, but it provides memory safety without usi…...

基于Linux定时任务实现的MySQL周期性备份

1、创建备份目录 sudo mkdir -p /var/backups/mysql/database_name2、创建备份脚本 sudo touch /var/backups/mysql/mysqldump.sh# 用VIM编辑脚本文件&#xff0c;写入备份命令 sudo vim /var/backups/mysql/mysqldump.sh# 内如如下 #!/bin/bash mysqldump -uroot --single-…...

【Altium Designer 20 笔记】隐藏PCB上的信号线(连接线)

使用网络类隐藏特定类型的信号线 如果你想要隐藏特定类型的信号线&#xff08;例如电源类&#xff09;&#xff0c;你可以首先创建一个网络类。使用快捷键DC调出对象类浏览器&#xff0c;在Net Classes中右击添加类&#xff0c;并重命名&#xff08;例如为“Power”&#xff0…...

【Git教程】(九)版本标签 —— 创建、查看标签,标签的散列值,将标签添加到日志输出中,判断标签是否包含特定的提交 ~

Git教程 版本标签&#xff08;tag&#xff09; 1️⃣ 创建标签2️⃣ 查看存在的标签3️⃣ 标签的散列值4️⃣ 将标签添加到日志输出中5️⃣ 判断tag是否包含特定的提交&#x1f33e; 总结 大多数项目都是用 1.7.3.2和 “ gingerbread” 这样的数字或名称来标识软件版本的。在 …...

MemberPress配置和使用会员登录页面

目录 隐藏 创建会员登录页面 编辑登录页面 设计您的登录页面 链接到您的登录页面 创建会员登录页面 要创建MemberPress会员登录页面&#xff0c;您需要做的就是导航到 MemberPress > 设置 > 页面选项卡&#xff0c;然后在页面顶部附近的“MemberPress 登录页面”…...

分享一个预测模型web APP的功能模块和界面的设计

一个临床预测模型web APP功能模块与界面设计 随着医疗技术的不断进步&#xff0c;web APP是临床预测模型在医学领域的应用的重要形式。这里分享一个web APP的设计&#xff0c;手里有医学预测模型的可以尝试将其构建成webAPP&#xff0c;进而在临床实践中体验预测模型带来的便利…...

智慧公厕是智慧城市建设中不可或缺的一部分

智慧城市的数字化转型正在取得显著成效&#xff0c;各项基础设施的建设也在迅速发展&#xff0c;其中智慧公厕成为了智慧城市体系中不可或缺的一部分。作为社会生活中必要的设施&#xff0c;公共厕所的信息化、数字化、智慧化升级转型能够实现全区域公共厕所管理的横向打通和纵…...

leetcode热题100.爬楼梯(从二进制到快速幂)

Problem: 70. 爬楼梯 文章目录 题目思路Code复杂度 题目 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

在WSL2的Ubuntu镜像中安装Docker

Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包&#xff1a; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?

在工业自动化持续演进的今天&#xff0c;通信网络的角色正变得愈发关键。 2025年6月6日&#xff0c;为期三天的华南国际工业博览会在深圳国际会展中心&#xff08;宝安&#xff09;圆满落幕。作为国内工业通信领域的技术型企业&#xff0c;光路科技&#xff08;Fiberroad&…...

Python 训练营打卡 Day 47

注意力热力图可视化 在day 46代码的基础上&#xff0c;对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...

基于单片机的宠物屋智能系统设计与实现(论文+源码)

本设计基于单片机的宠物屋智能系统核心是实现对宠物生活环境及状态的智能管理。系统以单片机为中枢&#xff0c;连接红外测温传感器&#xff0c;可实时精准捕捉宠物体温变化&#xff0c;以便及时发现健康异常&#xff1b;水位检测传感器时刻监测饮用水余量&#xff0c;防止宠物…...

PH热榜 | 2025-06-08

1. Thiings 标语&#xff1a;一套超过1900个免费AI生成的3D图标集合 介绍&#xff1a;Thiings是一个不断扩展的免费AI生成3D图标库&#xff0c;目前已有超过1900个图标。你可以按照主题浏览&#xff0c;生成自己的图标&#xff0c;或者下载整个图标集。所有图标都可以在个人或…...

字符串哈希+KMP

P10468 兔子与兔子 #include<bits/stdc.h> using namespace std; typedef unsigned long long ull; const int N 1000010; ull a[N], pw[N]; int n; ull gethash(int l, int r){return a[r] - a[l - 1] * pw[r - l 1]; } signed main(){ios::sync_with_stdio(false), …...