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

news2024/11/28 18:49:59

概述

上篇文章《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 {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 创建Filter时自动调用
        // 其中filterConfig包含这个Filter的配置参数,比如name之类的(从配置文件读取的)
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("拦截客户端发送来的请求.");
        chain.doFilter(request, response);
        System.out.println("拦截发送给客户端的响应.");
    }

    @Override
    public 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 times
        for(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 {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截客户端发送来的请求.");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("拦截发送给客户端的响应.");
    }

    @Override
    public 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;
    @Nullable
    private HandlerInterceptor[] interceptors;
    @Nullable
	private 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 constructor
				CollectionUtils.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.coloradmin.cn/o/1581763.html

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

相关文章

不到6毛钱的I2C总线实时时钟日历芯片LK8563

前言 8563实时时钟芯片&#xff0c;国内外均有多家生产&#xff0c;今推荐一个性价比极高的RTC芯片&#xff0c;LK8563&#xff0c;一片不到6毛钱. 特点 基于32.768kHz晶体的秒&#xff0c;分&#xff0c;小时&#xff0c;星期&#xff0c;天&#xff0c;月和年的计时 带有世…

【堡垒机】堡垒机的介绍

目前&#xff0c;常用的堡垒机有收费和开源两类。 收费的有行云管家、纽盾堡垒机&#xff1b; 开源的有jumpserver&#xff1b; 这几种各有各的优缺点&#xff0c;如何选择&#xff0c;大家可以根据实际场景来判断 什么是堡垒机 堡垒机&#xff0c;即在一个特定的网络环境下&…

第十届蓝桥杯大赛个人赛省赛(软件类) CC++ 研究生组-RSA解密

先把p&#xff0c;q求出来 #include<iostream> #include<cmath> using namespace std; typedef long long ll; int main(){ll n 1001733993063167141LL, sqr sqrt(n);for(ll i 2; i < sqr; i){if(n % i 0){printf("%lld ", i);if(i * i ! n) pri…

Matlab|【防骗贴】【免费】基于主从博弈的主动配电网阻塞管理

目录 1 主要内容 程序亮点 2 部分代码 3 程序结果 4 下载链接 1 主要内容 《基于主从博弈的主动配电网阻塞管理》文献介绍&#xff1a;主要采用一种配电网节点边际电价统一出清的主从博弈双层调度框架。上层框架解决用户在负荷聚合商引导下的用电成本最小化问题&#xff0…

电脑更新到win11后不能上网,更新win11后无法上网

越来越多的用户升级了win11系统使用&#xff0c;然而有些用户发现电脑更新到win11后不能上网了&#xff0c;这是怎么回事呢?而且奇怪的是&#xff0c;网络状态显示已连接&#xff0c;但就是无法上网&#xff0c;原本以为重置网络就能搞定&#xff0c;但结果相反。针对这一情况…

DRF的认证、权限、限流、序列化、反序列化

DRF的认证、权限、限流、序列化、反序列化 一、认证1、直接用&#xff0c;用户授权2、认证组件源码 二、权限1. 直接使用&#xff0c;用户权限2.权限组件源码 三、序列化1. 序列化1.1 自定义Serailizer类序列化1.2 在视图APIView中使用1.3 自定义ModelSerializer类序列化1.4 不…

vue3 +Taro 页面实现scroll-view 分页功能

需求 现在分页列表 后端只给你一个分页的数据列表 没有总页数 没有当前的分页 页数 只有这么一个list 、、、 如何去分页 我这使用的是scroll-view 组件 滑动到底部的事件 根据你当前设定的每页的数据数量和后端返回给你的数据列表数量 当某一次分页 两个数量不相等了以后 就…

ActiveMQ介绍及linux下安装ActiveMQ

ActiveMQ介绍 概述 ActiveMQ是Apache软件基金下的一个开源软件&#xff0c;它遵循JMS1.1规范&#xff08;Java Message Service&#xff09;&#xff0c;是消息队列服务&#xff0c;是面向消息中间件&#xff08;MOM&#xff09;的最终实现&#xff0c;它为企业消息传递提供高…

目标检测——YOLO系列学习(一)YOLOv1

YOLO可以说是单阶段的目标检测方法的集大成之作&#xff0c;必学的经典论文&#xff0c;从准备面试的角度来学习一下yolo系列。 YOLOv1 1.RCNN系列回顾 RCNN系列&#xff0c;无论哪种算法&#xff0c;核心思路都是Region Proposal&#xff08;定位&#xff09; classifier&am…

DJI无人机二次开发:模拟航线飞行

1.下载大疆行业调参软件&#xff08;大疆官网下载&#xff0c;有mac系统和win系统&#xff09;。 2.安装软件以后用数据线连接电脑和无人机 3.识别无人机点击进去进入模拟器设置和遥控器相同的经纬坐标 4.在遥控器上载入航线 5.开始执行以后在上云api可以看到无人机在地图上移动…

目标检测——3D车道数据集

一、重要性及意义 3D车道检测在自动驾驶和智能交通领域具有极其重要的地位&#xff0c;其重要性和意义主要体现在以下几个方面&#xff1a; 首先&#xff0c;3D车道检测可以精确判断车辆在道路上的位置、方向和速度&#xff0c;从而预测潜在的危险情况并及时采取措施。这种能…

数据结构速成--数据结构和算法

由于是速成专题&#xff0c;因此内容不会十分全面&#xff0c;只会涵盖考试重点&#xff0c;各学校课程要求不同 &#xff0c;大家可以按照考纲复习&#xff0c;不全面的内容&#xff0c;可以看一下小编主页数据结构初阶的内容&#xff0c;找到对应专题详细学习一下。 目录 一…

pdffactory pro 8注册码序列号下载 附教程

PdfFactory Pro可以说是一款行业专业且技术领先的的PDF虚拟打印机软件。其不仅占用系统内存小巧&#xff0c;功能强大&#xff0c;可支持用户无需使用Acrobat来创建Adobe PDF即可以进行PDF组件的创建和打印。同时&#xff0c;现在全新的PdfFactory Pro 8也正式上线来袭&#xf…

【资源分享】MAC上最好用的截图软件-Snipaste

::: block-1 “时问桫椤“是一个关注本科生到研究生教育阶段的不严肃的公众号&#xff0c;希望能在大家迷茫、难受、困难之时帮助到大家。用广大研究生的经验总结&#xff0c;让大家能尽早的适应研究生生活&#xff0c;尽快的看透科研本质。祝好&#xff01;&#xff01;&#…

Day94:云上攻防-云服务篇弹性计算云数据库实例元数据控制角色AK控制台接管

目录 云服务-弹性计算服务器-元数据&SSRF&AK 前提条件 利用环境1&#xff1a;获取某服务器权限后横向移动 利用环境2&#xff1a;某服务器上Web资产存在SSRF漏洞 云服务-云数据库-外部连接&权限提升 云上攻防-如何利用SSRF直接打穿云上内网 知识点&#xff1…

科技动态人工智能应用太空探索生物科技

根据最新的科技资讯&#xff0c;以下是一些值得关注的科技动态&#xff1a; 人工智能领域 智能体热潮 &#xff1a;随着大模型的研发热潮&#xff0c;AI智能体的发展迅速&#xff0c;它们被用作认知核心&#xff0c;具备强大的学习和迁移能力。智能体的架构和交互方式也在不断进…

vue快速入门(十五)监听键盘事件

注释很详细&#xff0c;直接上代码 上一篇 新增内容 特定按键监听事件全按键监听事件及两种判断方法 源码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthde…

03-JAVA设计模式-代理模式详解

代理模式 什么是代理模式 Java代理模式是一种常用的设计模式&#xff0c;主要用于在不修改现有类代码的情况下&#xff0c;为该类添加一些新的功能或行为。代理模式涉及到一个代理类和一个被代理类&#xff08;也称为目标对象&#xff09;。代理类负责控制对目标对象的访问&a…

蓝桥杯简单STL

目录 vector vector定义 vector访问 常用函数 size() ​编辑 push_back(num) pop_back() clear 迭代器&#xff08;iterator) 迭代器定义 遍历数组示例 insert(it, element) erase(it) 标准模板库--STL&#xff0c;它包含了多种预定义的容器、算法和迭代器&…

算法——倍增

. - 力扣&#xff08;LeetCode&#xff09; 给你一棵树&#xff0c;树上有 n 个节点&#xff0c;按从 0 到 n-1 编号。树以父节点数组的形式给出&#xff0c;其中 parent[i] 是节点 i 的父节点。树的根节点是编号为 0 的节点。 树节点的第 k 个祖先节点是从该节点到根节点路径…