63 | 职责链模式(下):框架中常用的过滤器、拦截器是如何实现的?
下载APP
关闭
渠道合作
推荐作者
63 | 职责链模式(下):框架中常用的过滤器、拦截器是如何实现的?
2020-03-27 王争 来自北京
《设计模式之美》
课程介绍
讲述:冯永吉
时长08:25大小7.72M
上一节课,我们学习职责链模式的原理与实现,并且通过一个敏感词过滤框架的例子,展示了职责链模式的设计意图。本质上来说,它跟大部分设计模式一样,都是为了解耦代码,应对代码的复杂性,让代码满足开闭原则,提高代码的可扩展性。
除此之外,我们还提到,职责链模式常用在框架的开发中,为框架提供扩展点,让框架的使用者在不修改框架源码的情况下,基于扩展点添加新的功能。实际上,更具体点来说,职责链模式最常用来开发框架的过滤器和拦截器。今天,我们就通过 Servlet Filter、Spring Interceptor 这两个 Java 开发中常用的组件,来具体讲讲它在框架开发中的应用。
话不多说,让我们正式开始今天的学习吧!
Servlet Filter
Servlet Filter 是 Java Servlet 规范中定义的组件,翻译成中文就是过滤器,它可以实现对 HTTP 请求的过滤功能,比如鉴权、限流、记录日志、验证参数等等。因为它是 Servlet 规范的一部分,所以,只要是支持 Servlet 的 Web 容器(比如,Tomcat、Jetty 等),都支持过滤器功能。为了帮助你理解,我画了一张示意图阐述它的工作原理,如下所示。
在实际项目中,我们该如何使用 Servlet Filter 呢?我写了一个简单的示例代码,如下所示。添加一个过滤器,我们只需要定义一个实现 javax.servlet.Filter 接口的过滤器类,并且将它配置在 web.xml 配置文件中。Web 容器启动的时候,会读取 web.xml 中的配置,创建过滤器对象。当有请求到来的时候,会先经过过滤器,然后才由 Servlet 来处理。
从刚刚的示例代码中,我们发现,添加过滤器非常方便,不需要修改任何代码,定义一个实现 javax.servlet.Filter 的类,再改改配置就搞定了,完全符合开闭原则。那 Servlet Filter 是如何做到如此好的扩展性的呢?我想你应该已经猜到了,它利用的就是职责链模式。现在,我们通过剖析它的源码,详细地看看它底层是如何实现的。
在上一节课中,我们讲到,职责链模式的实现包含处理器接口(IHandler)或抽象类(Handler),以及处理器链(HandlerChain)。对应到 Servlet Filter,javax.servlet.Filter 就是处理器接口,FilterChain 就是处理器链。接下来,我们重点来看 FilterChain 是如何实现的。
不过,我们前面也讲过,Servlet 只是一个规范,并不包含具体的实现,所以,Servlet 中的 FilterChain 只是一个接口定义。具体的实现类由遵从 Servlet 规范的 Web 容器来提供,比如,ApplicationFilterChain 类就是 Tomcat 提供的 FilterChain 的实现类,源码如下所示。
为了让代码更易读懂,我对代码进行了简化,只保留了跟设计思路相关的代码片段。完整的代码你可以自行去 Tomcat 中查看。
ApplicationFilterChain 中的 doFilter() 函数的代码实现比较有技巧,实际上是一个递归调用。你可以用每个 Filter(比如 LogFilter)的 doFilter() 的代码实现,直接替换 ApplicationFilterChain 的第 12 行代码,一眼就能看出是递归调用了。我替换了一下,如下所示。
这样实现主要是为了在一个 doFilter() 方法中,支持双向拦截,既能拦截客户端发送来的请求,也能拦截发送给客户端的响应,你可以结合着 LogFilter 那个例子,以及对比待会要讲到的 Spring Interceptor,来自己理解一下。而我们上一节课给出的两种实现方式,都没法做到在业务逻辑执行的前后,同时添加处理代码。
Spring Interceptor
刚刚讲了 Servlet Filter,现在我们来讲一个功能上跟它非常类似的东西,Spring Interceptor,翻译成中文就是拦截器。尽管英文单词和中文翻译都不同,但这两者基本上可以看作一个概念,都用来实现对 HTTP 请求进行拦截处理。
它们不同之处在于,Servlet Filter 是 Servlet 规范的一部分,实现依赖于 Web 容器。Spring Interceptor 是 Spring MVC 框架的一部分,由 Spring MVC 框架来提供实现。客户端发送的请求,会先经过 Servlet Filter,然后再经过 Spring Interceptor,最后到达具体的业务代码中。我画了一张图来阐述一个请求的处理流程,具体如下所示。
在项目中,我们该如何使用 Spring Interceptor 呢?我写了一个简单的示例代码,如下所示。LogInterceptor 实现的功能跟刚才的 LogFilter 完全相同,只是实现方式上稍有区别。LogFilter 对请求和响应的拦截是在 doFilter() 一个函数中实现的,而 LogInterceptor 对请求的拦截在 preHandle() 中实现,对响应的拦截在 postHandle() 中实现。
同样,我们还是来剖析一下,Spring Interceptor 底层是如何实现的。
当然,它也是基于职责链模式实现的。其中,HandlerExecutionChain 类是职责链模式中的处理器链。它的实现相较于 Tomcat 中的 ApplicationFilterChain 来说,逻辑更加清晰,不需要使用递归来实现,主要是因为它将请求和响应的拦截工作,拆分到了两个函数中实现。HandlerExecutionChain 的源码如下所示,同样,我对代码也进行了一些简化,只保留了关键代码。
在 Spring 框架中,DispatcherServlet 的 doDispatch() 方法来分发请求,它在真正的业务逻辑执行前后,执行 HandlerExecutionChain 中的 applyPreHandle() 和 applyPostHandle() 函数,用来实现拦截的功能。具体的代码实现很简单,你自己应该能脑补出来,这里就不罗列了。感兴趣的话,你可以自行去查看。
重点回顾
好了,今天的内容到此就讲完了。我们一块来总结回顾一下,你需要重点掌握的内容。
职责链模式常用在框架开发中,用来实现框架的过滤器、拦截器功能,让框架的使用者在不需要修改框架源码的情况下,添加新的过滤拦截功能。这也体现了之前讲到的对扩展开放、对修改关闭的设计原则。
今天,我们通过 Servlet Filter、Spring Interceptor 两个实际的例子,给你展示了在框架开发中职责链模式具体是怎么应用的。从源码中,我们还可以发现,尽管上一节课中我们有给出职责链模式的经典代码实现,但在实际的开发中,我们还是要具体问题具体对待,代码实现会根据不同的需求有所变化。实际上,这一点对于所有的设计模式都适用。
课堂讨论
前面在讲代理模式的时候,我们提到,Spring AOP 是基于代理模式来实现的。在实际的项目开发中,我们可以利用 AOP 来实现访问控制功能,比如鉴权、限流、日志等。今天我们又讲到,Servlet Filter、Spring Interceptor 也可以用来实现访问控制。那在项目开发中,类似权限这样的访问控制功能,我们该选择三者(AOP、Servlet Filter、Spring Interceptor)中的哪个来实现呢?有什么参考标准吗?
除了我们讲到的 Servlet Filter、Spring Interceptor 之外,Dubbo Filter、Netty ChannelPipeline 也是职责链模式的实际应用案例,你能否找一个你熟悉的并且用到职责链模式的框架,像我一样分析一下它的底层实现呢?
欢迎留言和我分享你的想法。如果有收获,欢迎你把这篇文章分享给你的朋友。
分享给需要的人,Ta购买本课程,你将得29元
生成海报并分享
赞 72
提建议
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
上一篇
62 | 职责链模式(上):如何实现可灵活扩展算法的敏感信息过滤框架?
下一篇
64 | 状态模式:游戏、工作流引擎中常用的状态机是如何实现的?
精选留言(60)
- cricket19812020-03-27Filter 可以拿到原始的http请求,但是拿不到你请求的控制器和请求控制器中的方法的信息; Interceptor 可以拿到你请求的控制器和方法,却拿不到请求方法的参数; Aop 可以拿到方法的参数,但是却拿不到http请求和响应的对象共 19 条评论168
- 万历十五年2020-11-28三者应用范围不同: web filter 作用于容器,应用范围影响最大;spring interceptor 作用于框架,范围影响适中;aop 作用于业务逻辑,精细化处理,范围影响最小。
作者回复: 嗯嗯 ������
共 5 条评论96 - PCMD2020-03-27针对问题1而言,其实要实现一个鉴权的过滤器,通过以上3种方式都是可以去实现的,然而从粒度,场景,和方式上边有有所区别,主要采取用哪个,还是有业务来决定去用,没有统一的参考标准。比如要对所有的web接口,进行统一的权限处理,不需要区分动作,写或者读,所有一视同仁,这种情况下,servlet的更加适合。针对一些存在状态的,比如做一些统一的去参数转换,cookie转uid之类,以及通用检验uid是否符合当前权限,则很用mvc较好,而aop粒度就可以分的更加细致了,在一些更新需要,查询不需要的,如分控,日志记录等,就比较适合展开共 1 条评论61
- webmin2020-03-29AOP、Servlet Filter、Spring Interceptor这三者可以从不同权限检查的范围大小的视角来应用: 1. Servlet Filter 运维部门需要对只供内部访问的服务进行IP限制或访问审查时,在容器这一层增加一个Filter,在发布时发布系统自动加挂这个Filter,这样对上层应用就是透明的,内网IP地址段增减或审查规则调整都不需要上层应用的开发人员去关心。 2. Spring Interceptor 由框架或基础服务部门来提供的微服务间相互调用的授权检查时,可以提供统一的SDK,由程序员在需要的服务上配置。 3. AOP 业务应用内权限检查,可以把权限检查在统一模块中实现,通过配置由AOP加插拦截检查。展开共 1 条评论53
- 小晏子2020-03-27首先需要明确“访问控制功能”的粒度,如果访问控制功能要精确到每个请求,那么要使用AOP,AOP可以配置每个controller的访问权限。而Spring interceptor和servlet filter的粒度会粗一些,控制HttpRequest, HttpResponse的访问。另外servlet filter不能够使用 Spring 容器资源,只能在容器(如tomcat)启动时调用一次,而Spring Interceptor是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如 Service对象、数据源、事务管理等,通过IoC注入到Interceptor即可。相比较而言,Spring interceptor更灵活一些。展开共 2 条评论26
- 筱乐乐哦2020-03-271、个人感觉权限的话,属于api的调用,应该放在调用链比较靠前的位置,早发现早处理,所以用Servlet Filter会更好一些吧,如果是rpc层的话,例如dubbo,就需要 在实现filter的时候通过order吧filter得优先级提高一些,让这个filter先执行,个人感觉哈 2、Dubbo Filter的核心处理逻辑在ProtocolFilterWrapper类下的buildInvokerChain这个方法中,属于把所有的filter的类对象搞成一个list,通过遍历list去调用所有的filter,Netty ChannelPipeline我记得是一个双向链表,pipeline 中的节点的数据结构是 ChannelHandlerContext 类,每个 ChannelHandlerContext 包含一个 ChannelHandler这种,支持从头尾开始传播事件,也就是触发调用,也可以从中间节点进行调用,入栈(read)是从head开始传播,也就是开始依次调用,出栈(write)是从tail开始传播,倒着调用。感觉算是对责任链的一个拓展使用,记不清了,得去看看代码,如果说错了,欢迎指点展开共 3 条评论18
- 楊_宵夜2020-03-29针对问题1,一把泪水想起了项目中的坑. 个人觉得最大的不同还是生效粒度的问题. 1. Servlet Filter是针对Servlet容器里的方法都能生效. 就是说Servlet容器里就算要把Spring换成别的框架,鉴权代码依然能生效. 2. Spring开头的就只能在Spring中生效, 2.1. 但更好还是在interceptor,因为interceptor天然的设计背景就是[在请求前,在相应后.] 2.2. 如果用AOP实现,就很依赖于AOP的pointcut设置,一不小心就会在[一次请求响应里]执行了[多次重复的鉴权服务]……展开16
- xk_2020-04-26课后题1,当然是全部都是用啊。 filter,可以控制所有的请求,用来处理网络攻击什么的。 interceptor可以控制用户和非用户的登录啊。 AOP可以控制用户角色对方方法的访问权限。 详情请见shiro,或者spring security。12
- David2020-12-30Servlet Fillter,Spring Interceptor,Spring AOP三者粒度是越来越细的。根据业务场景的覆盖度选择。 1. 比如限流就可以在Filter层去做,因为全局都需要限流防止服务被压垮。 2. 用户是否登录权限等可以使用Interceptor做。 3. 细粒度到类或者方法的控制使用AOP去做,比如日志 事务 方法级别权限。8
- 李小四2020-03-31设计模式_63: # 作业 1. 这个问题确实不懂,看了大家的回答,了解到对于颗粒度的考虑。 2. 有一个Java/Kotlin的网络库叫Okhttp,也用到了职责链模式,用来做一些日志记录等。 # 感想 第一次看Okhttp源码涉及Interceptor的时候,绕来绕去,一会儿Interceptor调用Chain,一会儿又反过来,云里雾里。。。看了几遍又画了图,才明白了流程。 当时不知道叫职责链模式,只是感觉写得很妙。展开6
- Geek_3e636e2020-12-18一个请求从客户端到服务端再到响应,假设Filter、Interceptor、AOP都存在,经过的路径大概是:请求->Filter->Interceptor->AOP->核心业务处理->AOP->Interceptor->Filter->响应。 Filter、Interceptor、 AOP在不同的节点所能感知到的数据状态都是不同的,姑且理解为域不同吧,要实现权限访问控制,肯定是在到达核心业务前植入权限控制逻辑,那就在“请求->Filter->Interceptor->AOP->核心业务处理”。 权限控制逻辑需要三个核心属性:资源、角色、角色资源映射。资源:一般我们用uri来标识某一个资源,或者可以通过注解等方式在方法上声明一个资源标识;角色和角色资源映射一般通过读取Session获取。那么权限控制逻辑放在那里取决于哪里可以拿到这两个信息?理论上角色和角色资源映射在哪里都可以读取到的。就看资源怎么表示了,如果你的资源是标识的servlet,那就通过Filter控制,如果你的资源是标识的Controller,可以在Interceptor控制,如果你的资源是标识的很深层的方法,可以在AOP控制展开5
- Ifdevil2020-05-11AOP的优势是灵活,万物皆可AOP,嘿嘿,但是职责链特性是链,更好处理一些连续处理的工作,个人浅见4
- Geek_27a2482020-03-30比如鉴权,限流,日志三个方面的话,鉴权可以使用spring interceptor,spring管理的获取的信息比较多,方便做更细的鉴权;限流的话可以使用servlet filter,执行比较考前,最大程度降低后方请求数量。日志的话使用aop,最大细度的记录日志信息。不知道这样理解的对不对,对这些的理解还是比较片面的不够深入,还有待加深5
- 马球先生2020-03-27安卓的网络请求框架 okhttp中使用了责任链共 1 条评论4
- test2020-03-27偏业务的使用AOP,全局的使用servlet filter4
- Xs.Ten2020-03-27即时通讯里面的消息分发可以用到责任链模式。可以添加不同的分发规则来分发不同的消息类型到各个消息处理器。4
- 忆水寒2020-04-23在Netty里面,pipeline组件就是使用职责链模式进行组装的。底层是双向链表(首尾节点是哨兵节点,用于处理某些buffer的释放)。详细可以参考这个文章 https://mp.weixin.qq.com/s/Lh1GnhYf36C2Y2gdBJvKQQ共 1 条评论3
- 落叶飞逝的恋2021-01-06卧槽。醍醐灌顶。怪不得之前的有的项目拦截器实现HandlerInterceptorAdapter来完成权限拦截,有的实现Filter来实现拦截。原来会先经过 Servlet Filter,然后再经过 Spring Interceptor。都在一个调用链路上,还有就是责任链模式原来这么简单。看了老师分享的源码,各类框架还是使用数组来存储处理器。真是有收获!!!2
- 袁帅2020-03-271、权限应该使用servlet filter , servlet filter 是对早被执行的,可为所有request加拦截,如果仅仅想对web请求加权限,那么使用spring interceptor2
- LiG❄️2020-03-27老师,请问Servlet Filter中采用递归方式调用职责链中元素,Spring Interceptor中采用数组变量方式调用职责链中元素,这两种调用方式,是基于什么考虑的,有什么优劣呢?2