设计模式——责任链模式

在 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
interface Handler {
boolean handle(Request req);
}

class HandlerA implements Handler {
@Override
public boolean handle(Request req) {
boolean handled = false;
// handle req...
return handled;
}
}

class HandlerB implements Handler {
@Override
public boolean handle(Request req) {
boolean handled = false;
// handle req...
return handled;
}
}

class HandlerChain {
private List<Handler> handlers = new ArrayList<>();

public void addHandler(Handler handler) {
handlers.add(handler);
}

public void handle(Request req) {
for(Handler h : handlers) {
if (h.handle(req)) return ; // if handled,then return
}
}
}

2.2 链表方式

链表方式比数组方式复杂一点,我们首先定义了一个抽象类 Handler, 它有一个成员变量 successor, 我们正是通过 successor 将所有的 Handler 连成一条链表。我们在 handle() 方法中运用了模版方法模式,这样做可以将模版代码抽象出来放在父类中,而子类只需要专注于自己的业务逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public abstract class Handler {
protected Handler successor;

public final void setSuccessor(Handler successor) {
this.successor = successor;
}

public final void handle(Request req) {
boolean handled = doHandle(req);
// if not handled and successor is not null, then pass req to succssor
if (!handled && successor != null) {
successor.handle(req);
}
}

protected abstract boolean doHandle(Request req);
}

class HandlerA extends Handler {

@Override
protected boolean doHandle(Request req) {
boolean handled = false;
// handle req
return handled;
}
}

class HandlerB extends Handler {

@Override
protected boolean doHandle(Request req) {
boolean handled = false;
// handle req
return handled;
}
}

class HandlerChain {
private Handler head;
private Handler tail;

public void addHandler(Handler handler) {
if (head == null) {
head = handler;
tail = handler;
return ;
}
tail.setSuccessor(handler);
tail = handler;
}

public void handle(Request req) {
if (head != null) head.handle(req);
}
}

3. 责任链模式的应用

在许多框架中都有责任链模式的身影,接下来我们以 Servlet 中的 Filter 和 Spring 中的 Interceptor 为例,来具体讲讲在框架中责任链模式是如何设计的。

3.1 Servlet Filter

在 Servlet Filter 中我们不仅仅可以对 request 进行拦截,我们还可以对 response 进行拦截,也就是说 Servlet Filter 可以双向拦截。不难看出,我们前面实现的责任链模式都是单向的,只能拦截 request。那么 Servlet Filter 是如何实现双向拦截的呢?为了更好地理解 Servlet Filter 设计的精妙之处,首先来看看我们是怎么使用 Filter 的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@WebFilter("/")
public class SubFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//...
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean handled = false;
// interceptor request...
// if not handled, let chain do the next filter processing
if (!handled) chain.doFilter(request, response);
// interceptor response...
}

@Override
public void destroy() {
//...
}
}

Servlet 是 Java EE 的一个规范,FilterChain 是其中一个接口,由具体的 web 容器提供实现类。ApplicationFilterChain 就是 Tomcat 提供的实现类。将 ApplicationFilterChain 进行简化和抽象,我们就可以得到下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final class ApplicationFilterChain implements FilterChain {
private int pos = 0; // the position of current processing filter
private int n; // the number of filters
private Filter[] filters;
private Servlet servlet;

@Override
public void doFilter(ServletRequest request, ServletResponse response) {
if (pos < n) {
Filter filter = filters[pos++];
filter.doFilter(request, response, this);
} else {
// request passed all filters, let servlet service the request.
servlet.service(request, response);
}
}
}

Servlet Filter 设计的精妙之处在于:Filter.doFilter() 方法和 FilterChain.doFilter() 方法组合在一起可以形成一个递归调用,并且利用了递归调用栈 FILO 的特性,实现了双向拦截的效果。(PS: 如果您还看不出怎么形成的递归,请务必将 Filter.doFilter() 的代码在 FilterChain.doFilter() 中展开…)

3.2 Spring Interceptor

Spring Interceptor 和 Servlet Filter 的功能非常类似,过滤器和拦截器本质上是没有区别的。要说它们有什么不同的话,那就是它们的作用时机不同了。

interceptor

我们还是和 Servlet Filter 一样,首先来看看我们是怎样使用 Spring Interceptor 的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SubInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
boolean permit = true;
// intercept request
return permit;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// intercept response
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// always execute
}
}

和 Servlet Filter 相比,Spring Interceptor 更加精细化了。Servlet Filter 拦截请求和拦截响应的的逻辑都写在了 Filter.doFilter() 方法中。而 Spring Interceptor 将它们分别放入了 preHandle() 和 postHandle() 里,并且还提供了 afterCompletion() 方法。

接下来我们来看看 Spring Interceptor 的底层是如何实现的。HandlerExecutionChain 就是串联 Spring Interceptor 的链子。它的实现相对 Tomcat 的 ApplicationFilterChain 来说,逻辑更加清晰,不需要使用递归来实现。同样,为了更好地体现设计逻辑,我们对代码做了些简化和抽象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class HandlerExecutionChain {
private final Object handler;
private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
private int interceptorIndex = -1;

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
for (int i = 0; i < this.interceptorList.size(); i++) {
HandlerInterceptor interceptor = this.interceptorList.get(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 {
for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
interceptor.postHandle(request, response, this.handler, mv);
}
}

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = this.interceptorList.get(i);
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}

在 Spring 框架中,我们是利用 DispatcherServlet.doDispatch() 方法来分发请求的,它在业务逻辑代码执行的前后,分别执行了 HandlerExecutionChain 中的 applyPreHandle() 和 applyPostHandle() 方法,以此来实现双向拦截的效果。

小结

这篇文章主要讲了与责任链设计模式相关的内容:为什么要使用责任链模式;责任链模式的原理;责任链模式的实现方式;以及责任链模式在具体框架中的应用。

0%