CategoryResourceRepost/极客时间专栏/设计模式之美/设计模式与范式:行为型/63 | 职责链模式(下):框架中常用的过滤器、拦截器是如何实现的?.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

227 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="63 | 职责链模式(下):框架中常用的过滤器、拦截器是如何实现的?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/6c/0c/6ce516539251f1a59ee783f50f07ff0c.mp3"></audio>
上一节课,我们学习职责链模式的原理与实现,并且通过一个敏感词过滤框架的例子,展示了职责链模式的设计意图。本质上来说,它跟大部分设计模式一样,都是为了解耦代码,应对代码的复杂性,让代码满足开闭原则,提高代码的可扩展性。
除此之外我们还提到职责链模式常用在框架的开发中为框架提供扩展点让框架的使用者在不修改框架源码的情况下基于扩展点添加新的功能。实际上更具体点来说职责链模式最常用来开发框架的过滤器和拦截器。今天我们就通过Servlet Filter、Spring Interceptor这两个Java开发中常用的组件来具体讲讲它在框架开发中的应用。
话不多说,让我们正式开始今天的学习吧!
## Servlet Filter
Servlet Filter是Java Servlet规范中定义的组件翻译成中文就是过滤器它可以实现对HTTP请求的过滤功能比如鉴权、限流、记录日志、验证参数等等。因为它是Servlet规范的一部分所以只要是支持Servlet的Web容器比如Tomcat、Jetty等都支持过滤器功能。为了帮助你理解我画了一张示意图阐述它的工作原理如下所示。
<img src="https://static001.geekbang.org/resource/image/32/21/3296abd63a61ebdf4eff3a6530979e21.jpg" alt="">
在实际项目中我们该如何使用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(&quot;拦截客户端发送来的请求.&quot;);
chain.doFilter(request, response);
System.out.println(&quot;拦截发送给客户端的响应.&quot;);
}
@Override
public void destroy() {
// 在销毁Filter时自动调用
}
}
// 在web.xml配置文件中如下配置
&lt;filter&gt;
&lt;filter-name&gt;logFilter&lt;/filter-name&gt;
&lt;filter-class&gt;com.xzg.cd.LogFilter&lt;/filter-class&gt;
&lt;/filter&gt;
&lt;filter-mapping&gt;
&lt;filter-name&gt;logFilter&lt;/filter-name&gt;
&lt;url-pattern&gt;/*&lt;/url-pattern&gt;
&lt;/filter-mapping&gt;
```
从刚刚的示例代码中我们发现添加过滤器非常方便不需要修改任何代码定义一个实现javax.servlet.Filter的类再改改配置就搞定了完全符合开闭原则。那Servlet Filter是如何做到如此好的扩展性的呢我想你应该已经猜到了它利用的就是职责链模式。现在我们通过剖析它的源码详细地看看它底层是如何实现的。
在上一节课中我们讲到职责链模式的实现包含处理器接口IHandler或抽象类Handler以及处理器链HandlerChain。对应到Servlet Filterjavax.servlet.Filter就是处理器接口FilterChain就是处理器链。接下来我们重点来看FilterChain是如何实现的。
不过我们前面也讲过Servlet只是一个规范并不包含具体的实现所以Servlet中的FilterChain只是一个接口定义。具体的实现类由遵从Servlet规范的Web容器来提供比如ApplicationFilterChain类就是Tomcat提供的FilterChain的实现类源码如下所示。
为了让代码更易读懂我对代码进行了简化只保留了跟设计思路相关的代码片段。完整的代码你可以自行去Tomcat中查看。
```
public final class ApplicationFilterChain implements FilterChain {
private int pos = 0; //当前执行到了哪个filter
private int n; //filter的个数
private ApplicationFilterConfig[] filters;
private Servlet servlet;
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
if (pos &lt; n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
filter.doFilter(request, response, this);
} else {
// filter都处理完毕后执行servlet
servlet.service(request, response);
}
}
public void addFilter(ApplicationFilterConfig filterConfig) {
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()函数的代码实现比较有技巧实际上是一个递归调用。你可以用每个Filter比如LogFilter的doFilter()的代码实现直接替换ApplicationFilterChain的第12行代码一眼就能看出是递归调用了。我替换了一下如下所示。
```
@Override
public void doFilter(ServletRequest request, ServletResponse response) {
if (pos &lt; n) {
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = filterConfig.getFilter();
//filter.doFilter(request, response, this);
//把filter.doFilter的代码实现展开替换到这里
System.out.println(&quot;拦截客户端发送来的请求.&quot;);
chain.doFilter(request, response); // chain就是this
System.out.println(&quot;拦截发送给客户端的响应.&quot;)
} else {
// filter都处理完毕后执行servlet
servlet.service(request, response);
}
}
```
这样实现主要是为了在一个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最后到达具体的业务代码中。我画了一张图来阐述一个请求的处理流程具体如下所示。
<img src="https://static001.geekbang.org/resource/image/fe/68/febaa9220cb9ad2f0aafd4e5c3c19868.jpg" alt="">
在项目中我们该如何使用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(&quot;拦截客户端发送来的请求.&quot;);
return true; // 继续后续的处理
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(&quot;拦截发送给客户端的响应.&quot;);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(&quot;这里总是被执行.&quot;);
}
}
//在Spring MVC配置文件中配置interceptors
&lt;mvc:interceptors&gt;
&lt;mvc:interceptor&gt;
&lt;mvc:mapping path=&quot;/*&quot;/&gt;
&lt;bean class=&quot;com.xzg.cd.LogInterceptor&quot; /&gt;
&lt;/mvc:interceptor&gt;
&lt;/mvc:interceptors&gt;
```
同样我们还是来剖析一下Spring Interceptor底层是如何实现的。
当然它也是基于职责链模式实现的。其中HandlerExecutionChain类是职责链模式中的处理器链。它的实现相较于Tomcat中的ApplicationFilterChain来说逻辑更加清晰不需要使用递归来实现主要是因为它将请求和响应的拦截工作拆分到了两个函数中实现。HandlerExecutionChain的源码如下所示同样我对代码也进行了一些简化只保留了关键代码。
```
public class HandlerExecutionChain {
private final Object handler;
private HandlerInterceptor[] interceptors;
public void addInterceptor(HandlerInterceptor interceptor) {
initInterceptorList().add(interceptor);
}
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i &lt; interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
triggerAfterCompletion(request, response, null);
return false;
}
}
}
return true;
}
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = interceptors.length - 1; i &gt;= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = this.interceptorIndex; i &gt;= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
} catch (Throwable ex2) {
logger.error(&quot;HandlerInterceptor.afterCompletion threw exception&quot;, ex2);
}
}
}
}
}
```
在Spring框架中DispatcherServlet的doDispatch()方法来分发请求它在真正的业务逻辑执行前后执行HandlerExecutionChain中的applyPreHandle()和applyPostHandle()函数,用来实现拦截的功能。具体的代码实现很简单,你自己应该能脑补出来,这里就不罗列了。感兴趣的话,你可以自行去查看。
## 重点回顾
好了,今天的内容到此就讲完了。我们一块来总结回顾一下,你需要重点掌握的内容。
职责链模式常用在框架开发中,用来实现框架的过滤器、拦截器功能,让框架的使用者在不需要修改框架源码的情况下,添加新的过滤拦截功能。这也体现了之前讲到的对扩展开放、对修改关闭的设计原则。
今天我们通过Servlet Filter、Spring Interceptor两个实际的例子给你展示了在框架开发中职责链模式具体是怎么应用的。从源码中我们还可以发现尽管上一节课中我们有给出职责链模式的经典代码实现但在实际的开发中我们还是要具体问题具体对待代码实现会根据不同的需求有所变化。实际上这一点对于所有的设计模式都适用。
## 课堂讨论
1. 前面在讲代理模式的时候我们提到Spring AOP是基于代理模式来实现的。在实际的项目开发中我们可以利用AOP来实现访问控制功能比如鉴权、限流、日志等。今天我们又讲到Servlet Filter、Spring Interceptor也可以用来实现访问控制。那在项目开发中类似权限这样的访问控制功能我们该选择三者AOP、Servlet Filter、Spring Interceptor中的哪个来实现呢有什么参考标准吗
1. 除了我们讲到的Servlet Filter、Spring Interceptor之外Dubbo Filter、Netty ChannelPipeline也是职责链模式的实际应用案例你能否找一个你熟悉的并且用到职责链模式的框架像我一样分析一下它的底层实现呢
欢迎留言和我分享你的想法。如果有收获,欢迎你把这篇文章分享给你的朋友。