在 web 开发中,有这样一个经典的场景:Sender 将 request 发送给 Receiver 处理,Receiver 在真正调用 service() 方法之前,经常需要进行各种校验,比如:鉴权,限流,敏感词过滤…当然我们可以在 Receiver 中用大量的 if 语句进行校验,但是这样代码的耦合度太高,不利于灵活地组合各种校验条件,也不利于扩展。
那遇到这样的场景,我们该如何设计呢?这就是我们今天要讲的——责任链模式(Chain of Responsibility)。
1. 责任链模式的原理
在上面的场景中,Receiver 类太过庞大和复杂,因此我们必须对它进行拆分,拆分成多个 Handler 对象,并将这多个 Handler 对象组成一条链,依次处理 request。其中每个 Handler 只专注于一个功能,比如说有鉴权的 Handler,有限流的 Handler,有敏感词过滤的 Handler…
这样做不仅仅符合高内聚、低耦合的设计原则;在框架的开发中,我们还可以利用责任链模式为框架提供扩展点,比如 Servlet 中的 Filter,Spring 中的 Interceptor 等。
2. 责任链模式的实现
责任链模式的实现灵活多变,根据组成链的方式不同,我们大体可以分为两类:数组方式和链表方式。
2.1 数组方式
数组方式比较简单直接,我们将所有的 Handler 都放入数组中,然后遍历数组,若其中某个 Handler 能处理这个请求,处理完成后直接返回。
1 | interface Handler { |
2.2 链表方式
链表方式比数组方式复杂一点,我们首先定义了一个抽象类 Handler, 它有一个成员变量 successor, 我们正是通过 successor 将所有的 Handler 连成一条链表。我们在 handle() 方法中运用了模版方法模式,这样做可以将模版代码抽象出来放在父类中,而子类只需要专注于自己的业务逻辑。
1 | public abstract class Handler { |
3. 责任链模式的应用
在许多框架中都有责任链模式的身影,接下来我们以 Servlet 中的 Filter 和 Spring 中的 Interceptor 为例,来具体讲讲在框架中责任链模式是如何设计的。
3.1 Servlet Filter
在 Servlet Filter 中我们不仅仅可以对 request 进行拦截,我们还可以对 response 进行拦截,也就是说 Servlet Filter 可以双向拦截。不难看出,我们前面实现的责任链模式都是单向的,只能拦截 request。那么 Servlet Filter 是如何实现双向拦截的呢?为了更好地理解 Servlet Filter 设计的精妙之处,首先来看看我们是怎么使用 Filter 的。
1 |
|
Servlet 是 Java EE 的一个规范,FilterChain 是其中一个接口,由具体的 web 容器提供实现类。ApplicationFilterChain 就是 Tomcat 提供的实现类。将 ApplicationFilterChain 进行简化和抽象,我们就可以得到下面的代码:
1 | public final class ApplicationFilterChain implements FilterChain { |
Servlet Filter 设计的精妙之处在于:Filter.doFilter() 方法和 FilterChain.doFilter() 方法组合在一起可以形成一个递归调用,并且利用了递归调用栈 FILO 的特性,实现了双向拦截的效果。(PS: 如果您还看不出怎么形成的递归,请务必将 Filter.doFilter() 的代码在 FilterChain.doFilter() 中展开…)
3.2 Spring Interceptor
Spring Interceptor 和 Servlet Filter 的功能非常类似,过滤器和拦截器本质上是没有区别的。要说它们有什么不同的话,那就是它们的作用时机不同了。
我们还是和 Servlet Filter 一样,首先来看看我们是怎样使用 Spring Interceptor 的。
1 | public class SubInterceptor implements HandlerInterceptor { |
和 Servlet Filter 相比,Spring Interceptor 更加精细化了。Servlet Filter 拦截请求和拦截响应的的逻辑都写在了 Filter.doFilter() 方法中。而 Spring Interceptor 将它们分别放入了 preHandle() 和 postHandle() 里,并且还提供了 afterCompletion() 方法。
接下来我们来看看 Spring Interceptor 的底层是如何实现的。HandlerExecutionChain 就是串联 Spring Interceptor 的链子。它的实现相对 Tomcat 的 ApplicationFilterChain 来说,逻辑更加清晰,不需要使用递归来实现。同样,为了更好地体现设计逻辑,我们对代码做了些简化和抽象:
1 | public class HandlerExecutionChain { |
在 Spring 框架中,我们是利用 DispatcherServlet.doDispatch() 方法来分发请求的,它在业务逻辑代码执行的前后,分别执行了 HandlerExecutionChain 中的 applyPreHandle() 和 applyPostHandle() 方法,以此来实现双向拦截的效果。
小结
这篇文章主要讲了与责任链设计模式相关的内容:为什么要使用责任链模式;责任链模式的原理;责任链模式的实现方式;以及责任链模式在具体框架中的应用。