设计模式学习笔记 - 设计模式与范式 -行为型:7.责任链模式(下):框架中常用的过滤器、拦截器是如何实现的?

概述

上篇文章《6.责任链模式(上):原理与实现》,学习了职责链模式的原理与实现,并且通过一个敏感词过滤框架的例子,展示了职责链模式的设计意图。本质上来说,它跟大部分设计模式一样,都是为了解耦代码,应对代码的复杂性,让代码满足开闭原则,提高代码的可扩展性。

此外,我们还提到,在职责链模式常用在框架的开发中,为框架提供扩展点,让框架的使用者在不修改框架源码的情况下,基于扩展点添加新功能。实际上,更具体点来说,职责链模式最常用来开发框架的过滤器和拦截器。本章,我们通过 Servlet Filter、Spring Interceptor 这两个 Java 开发中常用的组件,来具体讲讲它在框架开发中的应用。


Servlet Filter

Servlet Filter 是 Java Servlet 规范中定义的组件,翻译成中文就是过滤器的意思,它可以实现对 HTTP 请求的过滤功能,比如鉴权、限流、记录日志、验证参数等等。因为它是 Servlet 规范的一部分,所以,只要是支持 Servlet 的容器(比如 Tomcat、Jetty),都支持过滤器功能。为了帮助你理解,下面绘制了一张示意图阐述它的工作原理,如下所示。
在这里插入图片描述
在实际项目中,我们该如何使用 Servlet Filter 呢?下面写了个简单的示例代码。添加一个过滤器,我们只需要定义一个实现 javax.servlet.Filter 接口的过滤器类,并且将它配置在 web.xml 配置文件中。Web 容器启动时,会读取 web.xml 中的配置,创建过滤器对象。当有请求到来时,会先经过过滤器,然后才由 Servlet 来处理。

public class LogFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {// 创建Filter时自动调用// 其中filterConfig包含这个Filter的配置参数,比如name之类的(从配置文件读取的)}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {System.out.println("拦截客户端发送来的请求.");chain.doFilter(request, response);System.out.println("拦截发送给客户端的响应.");}@Overridepublic void destroy() {// 在销毁Filter时自动调用}
}// 在web.xml配置中如下配置:
<filter><filter-name>logFilter</filter-name><filter-class>com.example.LogFilter</filter-class>
</filter>
<filter-mapping><filter-name>logFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>

从刚刚的示例代码中,我们发现,添加过滤器非常方便,不需要修改任何代码,定义一个实现 javax.servlet.Filter 的类,在改改配置就搞定了,完全符合开闭原则。那 Servlet Filter 是如何做到如此好的扩展性呢?那就是利用职责链模式。现在,我们通过剖析它的源码,详细地看看它的底层是如何实现的。

上篇文章,讲到职责链模式的实现包含处理器接口(IHandler)或抽象类(Handler),以及处理器链(HandlerChain)。对应到 Servlet Filter,javax.servlet.Filter 就是处理接口,FilterChain 就是处理器链。接下来,重点看下 FilterChain 是如何实现的。

不过,之前也讲过,Servlet 只是一个规范,并不包含具体的实现,所以 Servlet 中的 FilterChain 只是一个接口定义。具体的实现类由遵从 Servlet 规范的 Web 容器来提供,比如,ApplicationFilterChain 类就是 Tomcat 提供的 FilterChain 的实现类,源码如下所示。

为了让代码更易懂,对代码进行了简化,只保留了跟设计思路相关的代码片段。完整代码你可以自行去 Tomcat 查看。

public final class ApplicationFilterChain implements FilterChain {// ...private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];// ...private Servlet servlet = null;// ...@Override public void doFilter(ServletRequest request, ServletResponse response) {// doFilter进行了简化,完整版可以查看 Tomcat源码if (pos < n) { ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = filterConfig.getFilter(); filter.doFilter(request, response, this); } else { // filter都处理完毕后,执行servlet servlet.service(request, response); } }// ...void addFilter(ApplicationFilterConfig filterConfig) {// Prevent the same filter being added multiple timesfor(ApplicationFilterConfig filter:filters)if(filter==filterConfig)return;if (n == filters.length) {ApplicationFilterConfig[] newFilters =new ApplicationFilterConfig[n + INCREMENT];System.arraycopy(filters, 0, newFilters, 0, n);filters = newFilters;}filters[n++] = filterConfig;}// ...
}

ApplicationFilterChain 中的 doFilter() 函数实现比较有技巧,实际上是一个递归调用。

这样实现主要是为了在一个 doFilter() 方法中,支持双休拦截,既能拦截客户端发送来的请求,也能拦截发送给客户端的响应,你可以结合者 LogFilter 的例子,以及对比待会要讲的 Spring Interceptor,来自己理解下。

Spring Interceptor

Spring Interceptor 翻译成中文就是拦截器。尽管和 Servlet Filter 的英文单词和中文翻译都不同,但两者基本上可以看作是一个概念,都用来实现对 HTTP 请求进行拦截处理。

它们不同之处在于, Servlet Filter 是 Servlet 规范的一部分,实现依赖于 Web 容器。Spring Interceptor 是 Spring MVC 框架的一部分,由 Spring MVC 框架来提供实现。客户端发送请求会先经过 Servlet Filter ,然后再经过 Spring Interceptor,最后达到具体的业务代码中。我绘制了一张示意图来展示一个请求的处理流程。

在这里插入图片描述
在项目中,该如何使用 Spring Interceptor 呢?我写了个简单的示例代码,如下所示。 LogInterceptor 实现和刚刚 LogFilter 完全相同的功能。LogFilter 对请求和响应的拦截是在 doFilter() 中实现的,而 LogInterceptor 对请求的拦截在 preHandle() 中实现,对响应的拦截在 postHandle() 中实现。

public class LogInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("拦截客户端发送来的请求.");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("拦截发送给客户端的响应.");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("这里总是被执行.");}
}//在Spring MVC配置文件中配置interceptors
<mvc:interceptors>   <mvc:interceptor>       <mvc:mapping path="/*"/>       <bean class="com.example.LogInterceptor" />   </mvc:interceptor>
</mvc:interceptors>

同样,我们来剖析下, Spring Interceptor 底层是如何实现的。

当然,它也是基于职责链模式实现的。其中,HandlerExecutionChain 类是职责链模式中的处理器链。它的实现相较于 Tomcat 中的 ApplicantionFilterChain 来说,逻辑更加清晰,不需要使用递归来实现,主要是因为它将请求和响应的拦截工作,拆分到了两个函数中实现。HandlerExecutionChain 的源码如下所示,同样地,对代码也进行了简化,只保留了关键代码。

public class HandlerExecutionChain {// ...private final Object handler;@Nullableprivate HandlerInterceptor[] interceptors;@Nullableprivate List<HandlerInterceptor> interceptorList;// ...public void addInterceptor(HandlerInterceptor interceptor) {initInterceptorList().add(interceptor);}public void addInterceptors(HandlerInterceptor... interceptors) {if (!ObjectUtils.isEmpty(interceptors)) {CollectionUtils.mergeArrayIntoCollection(interceptors, initInterceptorList());}}private List<HandlerInterceptor> initInterceptorList() {if (this.interceptorList == null) {this.interceptorList = new ArrayList<>();if (this.interceptors != null) {// An interceptor array specified through the constructorCollectionUtils.mergeArrayIntoCollection(this.interceptors, this.interceptorList);}}this.interceptors = null;return this.interceptorList;}// ...boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = 0; i < interceptors.length; i++) {HandlerInterceptor interceptor = interceptors[i];if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}}return true;}void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = interceptors.length - 1; i >= 0; i--) {HandlerInterceptor interceptor = interceptors[i];interceptor.postHandle(request, response, this.handler, mv);}}}void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)throws Exception {HandlerInterceptor[] interceptors = getInterceptors();if (!ObjectUtils.isEmpty(interceptors)) {for (int i = this.interceptorIndex; i >= 0; i--) {HandlerInterceptor interceptor = interceptors[i];try {interceptor.afterCompletion(request, response, this.handler, ex);}catch (Throwable ex2) {logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);}}}}// ...
}

在 Spring 框架中,DispatcherServletdoDispatch() 方法来分发请求,它在真正的业务逻辑执行前后,执行 HandlerExecutionChain 中的 applyPreHandle()applyPostHandle() 函数,用来实现拦截的功能。具体的代码实现很简单,你自己应该可以脑补出来,这里就不罗列了。

总结

职责链模式常用在空间开发中,用来实现矿机的过滤器、拦截器功能,让框架的使用者在不需要修改框架源码的情况下,添加新的过滤拦截功能。这也体现了之前讲到的对扩展开发、对修改关闭的设计原则。

本章,通过 Servlet Filter、Spring Interceptor 两个实际的例子,给你展示了在框架开发中职责链模式具体是怎么应用的。从源码中,可以发现,尽管《6.责任链模式(上):原理与实现》给出了代码实现,但在实际的开发中,还是要具体问题具体对待,代码实现胡已根据不同的需求有所变化。实际上,这一点对所有的设计模式都适用。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python从0到100(十一):Python字典介绍及运用

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…

10 spring-data-redis 中创建的 pipe 和 anon_inode 的 fd 来自于哪里?

前言 本文的问题 主要是 衍生自 spring-boot-acurator 定时检测 redis 集群导致 “IOException: Too many open files“ 对于这里 pipe 的使用, 也是很神奇, 因为 貌似没有用过这类 api 然后 这里调研一下, 然后 追根究底到最终, 是到了 jdk 的 c 代码的调用, 创建的 pipe …

51单片机之LED点阵屏

目录 1.LED点阵屏简介 2.配置LED点阵屏代码 1.LED点阵屏简介 LED点阵屏真的是遍布我们我们生活的每个角落&#xff0c;从街边的流动显示字的招牌到你的液晶显示屏&#xff0c;都是基于点阵屏的原理研究出来的。还有那个世界上最大的球状建筑物&#xff1a;MSG Sphere&#xff…

三极管结构难?——秒了

前边我们已经学完了PN结&#xff0c;二极管&#xff0c;在分析了二极管后&#xff0c;我们对这些东西有了一定深度的了解&#xff0c;但是只给我们一个二极管去研究&#xff0c;这玩意好像真的没啥大用&#xff0c;其实我们追求的是用半导体材料去代替电子管的放大作用&#xf…

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

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

tensorflow.js 使用 opencv.js 将人脸特征点网格绘制与姿态估计线绘制结合起来,以获得更高的帧数

系列文章目录 如何在前端项目中使用opencv.js | opencv.js入门如何使用tensorflow.js实现面部特征点检测tensorflow.js 如何从 public 路径加载人脸特征点检测模型tensorflow.js 如何使用opencv.js通过面部特征点估算脸部姿态并绘制示意图 文章目录 系列文章目录前言一、实现步…

Lecture 2~4 About Filter

文章目录 空间域上的滤波器- 线性滤波器盒状滤波器Box Filter锐化Sharpening相关运算 vs. 卷积运算 Correlation vs. Convolution - 非线性滤波器高斯滤波器Gaussian filter - 实际问题- 纹理texture 频域上的滤波器 滤波的应用- 模板匹配- 图像金字塔 空间域上的滤波器 图像…

Django的中间件

Django的中间件 【一】重点&#xff1a; django中间件是django的门户 请求来的时候需要经过中间件才能到达真正的django后端响应走的时候也需要经过中间件才能发送出去中间件按照顺序依次执行 ​ Django 中间件&#xff08;Middleware&#xff09;是 Django 框架提供的一种…

设计模式——代理模式12

代理模式给某对象提供一个代理对象&#xff0c;由代理对象来控制对原对象的引用。该模式经常出现在系统框架或相关组件中&#xff0c;如Spring框架如何解决循环依赖&#xff0c;在Mybatis 定义 Dao 层相关接口 不写实现 如何通过注解或者xml映射到对应到sql语句。下面介绍 静态…

再见 MybatisPlus,阿里推出新 ORM 框架更牛X

最近看到一个 ORM 框架 Fluent Mybatis 挺有意思的&#xff0c;整个设计理念非常符合工程师思维。 我对官方文档的部分内容进行了简单整理&#xff0c;通过这篇文章带你看看这个新晋 ORM 框架。 官方文档&#xff1a;https://gitee.com/fluent-mybatis/fluent-mybatis/wikis 提…

Nginx反向代理与Tomcat实现ssm项目前后端分离部署

Nginx nginx是一款http和支持反向代理的web服务器&#xff0c;以其优越的性能被广泛使用。以下是百度百科的介绍。 Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔赛索耶夫为俄罗斯访问量第二的Rambler.…

智慧园区水电能源监控管理系统

随着智慧城市的快速发展&#xff0c;智慧园区作为城市智能化的重要组成部分&#xff0c;其能源监控管理系统显得尤为关键。智慧园区水电能源监控管理系统&#xff0c;是利用先进的信息技术和自动控制技术&#xff0c;对园区内的水电能源使用进行实时监控、管理和优化的综合性智…

美国34401A安捷伦数字万用表

181/2461/8938产品概述&#xff1a; 附加功能: 6 1/2位数分辨率10种测量功能:DC/交流电压、DC/交流电流、2线和4线电阻、二极管、连续性、频率、周期基本精度:0.0035% DC&#xff0c;0.06%交流1000 V最大电压输入&#xff0c;3 A最大电流输入每秒1000次读数512读取记忆 安捷…

Linux C++ 027-STL之deque容器

Linux C 027-STL之deque容器 本节关键字&#xff1a;Linux、C、deque 相关库函数&#xff1a;pubsh_back、begin、front、sort deque基本概念 功能&#xff1a;双端数组&#xff0c;可以对头端进行插入删除操作。 deque 与 vector 的区别&#xff1a; &#xff08;1&#x…

vue将html生成pdf并分页

jspdf html2canvas 此方案有很多的css兼容问题&#xff0c;比如虚线边框、svg、页数多了内容显示不全、部分浏览器兼容问题&#xff0c;光是解决这些问题就耗费了我不少岁月和精力 后面了解到新的技术方案&#xff1a; jspdf html-to-image npm install --save html-to-i…

关于pandas 无法读取 csv 文件数据的解决方式

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 …

LabVIEW和2D激光扫描的受电弓滑板磨耗精确测量

LabVIEW和2D激光扫描的受电弓滑板磨耗精确测量 在电气化铁路运输中&#xff0c;受电弓滑板的健康状况对于保障列车安全行驶至关重要。受电弓滑板作为连接电网与列车的直接介质&#xff0c;其磨损情况直接影响到电能的有效传输及列车的稳定运行。精确、快速测量受电弓滑板磨损情…

IntelliJ IDEA 2024.1安装与激活[破解]

一&#xff1a;IDEA官方下载 ①如题&#xff0c;先到IDEA官方下载&#xff0c;简简单单 ②IDEA官方&#xff1a;IntelliJ IDEA – the Leading Java and Kotlin IDE 二&#xff1a;获取脚本 &#x1f31f;网盘下载&#xff1a;jetbra (密码&#xff1a;lzh7) &#x1f31f;获取…

CLI的使用与IOS基本命令

1、实验目的 通过本实验可以掌握&#xff1a; CLI的各种工作模式个CLI各种编辑命令“?” 和【Tab】键使用方法IOS基本命令网络设备访问限制查看设备的相关信息 2、实验拓扑 CLI的使用与IOS基本命令使用拓扑如下图所示。 3、实验步骤 &#xff08;1&#xff09;CLI模式的切…

Visual Studio Code 终端为管理员权限

第一部 1、 Visual Studio Code 快捷方式启动选项加上管理员启动 第二步 管理员方式运行 powershell Windows 10的任务栏自带了搜索。或者开始菜单选搜索只需在搜索框中输入powershell。 在出来的搜索结果中右击Windows PowerShell&#xff0c;然后选择以管理员方式运行。 执…