设计模式之责任链讲解
责任链模式适用于需要将请求和处理解耦的场景,同时又需要动态地组织处理逻辑的场景。
通过使用责任链模式,可以实现请求的动态处理、灵活的扩展和简化的代码编写,提高系统的可维护性和可扩展性。
一、责任链入门
以下这是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
方法项过滤器链中增加了两个拦截器LoginInterceptor
和 AdminLoginInterceptor
。这两个拦截器通过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方法决定,数值越小的拦截器优先级越高。
四、总结
关于责任链模式,必须要有处理器模板,要有链,链可以是集合,也可以是类似链表。请求就会沿着这条链依次被链上处理器处理。
相关文章:
设计模式之责任链讲解
责任链模式适用于需要将请求和处理解耦的场景,同时又需要动态地组织处理逻辑的场景。 通过使用责任链模式,可以实现请求的动态处理、灵活的扩展和简化的代码编写,提高系统的可维护性和可扩展性。 一、责任链入门 以下这是GPT生成的责任链代…...
K8s: 将一个节点移出集群和相关注意事项
前置步骤 在Kubernetes集群中,要移出一个节点,你需要执行以下步骤: 1 )将节点标记为不可调度 首先,你需要将目标节点标记为不可调度,以确保Kubernetes不会在该节点上调度新的Pod这可以通过执行以下命令实…...

Python学习笔记24 - 学生信息管理系统
1. 需求分析 2. 系统设计 3. 系统开发必备 4. 主函数设计 5. 学生信息维护模块设计 a. 录入学生信息 b. 删除学生信息 c. 修改学生信息 d. 查询学生信息 e. 统计学生总人数 f. 显示所有学生信息 g. 排序模块设计 6. 项目打包...
【物联网应用案例】某制造企业电锅炉检测项目
供暖行业在我国的经济发展中占据着重要的地位,然而,长期以来,该行业存在着自动化水平低、管理效率不高等问题,制约了其持续发展。为了解决这些问题,吉林某电锅炉生产厂家进行了一项创新性的尝试。 该厂家通过集成物联…...

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

嵌入式学习52-ARM1
知识零散: 1.flash: nor flash 可被寻地址 …...

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

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

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

网络安全(防火墙,IDS,IPS概述)
问题一:什么是防火墙,IDS,IPS? 防火墙是对IP:port的访问进行限制,对访问端口进行制定的策略去允许开放的访问,将不放开的端口进行拒绝访问,从而达到充当防DDOS的设备。主要是拒绝网络流量,阻断所有不希望出现的流程,禁止数据流量流通,达到安全防护的作用。如将一些恶…...
安装IntelliJ IDEA插件教程
安装IntelliJ IDEA插件:一份详细指南 在提升IntelliJ IDEA开发效率的过程中,插件扮演着不可或缺的角色。它们为IDE提供了额外的功能和工具,以满足开发者在特定编程语言、框架、测试、版本控制等方面的个性化需求。本文将为您详细阐述如何在I…...
大厂基础面试题(之四)
Q1:请先进行自我介绍 Q2:说下你学习前端的详细过程 Q3:一个页面从url输入到显示页面的整个过程 1.URL解析 2.DNS解析 3.建立TCP连接 4.发起HTTP请求 5.服务器处理请求 6.返回HTTP响应 7.下载页面资源 8.解析和渲染页面 9.JavaScript执行 10…...
为什么我们应该切换到Rust
What is RUST? 什么是Rust? 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编辑脚本文件,写入备份命令 sudo vim /var/backups/mysql/mysqldump.sh# 内如如下 #!/bin/bash mysqldump -uroot --single-…...

【Altium Designer 20 笔记】隐藏PCB上的信号线(连接线)
使用网络类隐藏特定类型的信号线 如果你想要隐藏特定类型的信号线(例如电源类),你可以首先创建一个网络类。使用快捷键DC调出对象类浏览器,在Net Classes中右击添加类,并重命名(例如为“Power”࿰…...

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

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

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

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

leetcode热题100.爬楼梯(从二进制到快速幂)
Problem: 70. 爬楼梯 文章目录 题目思路Code复杂度 题目 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 示例 1: 输入:n 2 输出:2 解释:有两种方…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)
要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况,可以通过以下几种方式模拟或触发: 1. 增加CPU负载 运行大量计算密集型任务,例如: 使用多线程循环执行复杂计算(如数学运算、加密解密等)。运行图…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...

网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...
Python网页自动化Selenium中文文档
1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API,让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API,你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…...