当前位置: 首页 > 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;有两种方…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

关于uniapp展示PDF的解决方案

在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项&#xff1a; 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库&#xff1a; npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案

引言 在分布式系统的事务处理中&#xff0c;如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议&#xff08;2PC&#xff09;通过准备阶段与提交阶段的协调机制&#xff0c;以同步决策模式确保事务原子性。其改进版本三阶段提交协议&#xff08;3PC&#xf…...

React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构

React 实战项目&#xff1a;微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇&#xff01;在前 29 篇文章中&#xff0c;我们从 React 的基础概念逐步深入到高级技巧&#xff0c;涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...

leetcode_69.x的平方根

题目如下 &#xff1a; 看到题 &#xff0c;我们最原始的想法就是暴力解决: for(long long i 0;i<INT_MAX;i){if(i*ix){return i;}else if((i*i>x)&&((i-1)*(i-1)<x)){return i-1;}}我们直接开始遍历&#xff0c;我们是整数的平方根&#xff0c;所以我们分两…...