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

发布于:2024-04-10 ⋅ 阅读:(29) ⋅ 点赞:(0)

概述

上篇文章《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.责任链模式(上):原理与实现》给出了代码实现,但在实际的开发中,还是要具体问题具体对待,代码实现胡已根据不同的需求有所变化。实际上,这一点对所有的设计模式都适用。