行为型模式
责任链模式将多个对象连成一个链条,并沿着这条链传递请求,使得每个对象都有机会处理请求,直到有一个对象或者每个对象都能处理这个请求。
组成
多个对象要能组成一个“链表”结构,每个对象应该继承自同一个类或实现同一个接口,并且每个对象要持有下一个对象的引用。因此,责任链的组成如下图所示:
图中各个组成的作用:
- Handler:定义一个处理请求的接口,持有对自身的引用;
- ConcreteHandler:接口的具体实现,负责处理请求或转发请求,需要能判断“是否处理当前请求”;
从链中第一个对象开始,每个对象要么处理请求,要么转发给下一个对象。提交请求的对象(用户、客户端)并不知道哪一个对象会处理他的请求,只能保证他的请求一定会被处理(请求有一个隐式的接收者)。请求要能够沿着链条转发,并且保证接收者是隐式的,链上每个对象都要有一致的处理请求和转发给后一个对象的接口(有相同的对外方法)。
应用场景
当存在以下情况时,使用责任链 模式:
-
有多个对象可以处理一个请求,哪个对象处理该请求在运行时自动确定;
-
想在不明确指定接受者的情况下,向多个对象中的一个提交一个请求;
-
可处理一个请求的对象集合应该能够被动态指定;
示例代码
// Handler 接口
public interface Handler {
// 对后继者的引用
protected Handler next;
/**
* 处理方法
*/
public void handle(Request request);
}
// 实现类A
public class HandlerA implements Handler {
@Override
public void handle(Request request) {
// TODO 实现自己的处理逻辑
// ....
// 指派给下一个
if(next != null) {
next.handle(request);
}
}
}
// 实现类B
public class HandlerB implements Handler {
@Override
public void handle(Request request) {
// TODO 实现自己的处理逻辑
// ....
// 指派给下一个
if(next != null) {
next.handle(request);
}
}
}
// 把 HandlerA 和 HandlerB 组成一个链条
HandlerA handlerA = new HandlerA();
HandlerB handlerB = new HandlerB();
handlerA.setNext(handlerB);
过滤器
“责任链模式”的一个经典示例是容器的过滤器。
FilterChain
因为责任链中每个对象要能指向下一个对象,所以责任链的一个关键问题是:如何维护实现类之间的连接关系。上面的示例代码中,使用指向下一个对象的引用字段 next 来维护这种关系,过滤器使用一个对象javax.servlet.FilterChain 来维护一个对象数组。FilterChain 接口的 UML 如下图所示:
FilterChain代码如下:
public interface FilterChain {
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException;
}
FilterChain 只提供了一个方法,请求参数是 “request、response”,从前面对“责任链”的介绍可知,在该方法中既要要调用链中对象的过滤方法,还要能请求参数转发到链中的下一个对象。下面,通过它的实现类 ApplicationFilterChain 查看它的实现。
ApplicationFilterChain
public final class ApplicationFilterChain implements FilterChain {
/**
* Filters 数组
*/
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
/**
* Filters 数组处理请求的对象下标
*/
private int pos = 0;
/**
* Filters 数组中对象的个数
*/
private int n = 0;
// FilterChain Methods
@Override
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
// ...其他的省略,主要是调用了这个方法执行过滤....
internalDoFilter(req,res);
}
// 真正处理请求的方法
private void internalDoFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
// 如果当前下标 pos < n,就取出当前对象,执行对象的 doFilter() 方法
// 注意:pos++
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
Filter filter = filterConfig.getFilter();
if( Globals.IS_SECURITY_ENABLED ) {
// 省略...
} else {
// 执行过滤器方法
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}
}
/**
* 把一个过滤器添加到 filters 数组中
*/
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;
}
}
从源码得到以下信息:
- 有一个
ApplicationFilterConfig数组(对象数组),每个ApplicationFilterConfig对象持有一个Filter变量; - 参数
pos、n分别表示当前数组中正在使用的过滤器下标和数组的总长度,每执行一次方法,pos 值就会加1; - 在启动时,
addFilter()方法把ApplicationFilterConfig对象添加到数组中,并且保证了不会重复添加; - 在
internalDoFilter()方法中,取出数组中pos下标处的filterConfig和filter,然后执行filter对象doFilter()方法;
在上面的代码中,并没有看到当前对象把请求转发给下一个对象(执行了 pos++,把数组下标加1),对象只执行了自身的doFileter() 方法,很显然转发操作就发生在这个 Filter 对象的doFileter() 方法中。
Filter
Filter 接口的源码如下:
public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException {}
// 除了 FilterChain 中的两个参数,还传入了 FilterChain 对象
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
public default void destroy() {}
}
doFilter() 方法中传入了3个参数:request、response、FilterChain。
进入到最简单的 Filter 实现类 SessionInitializerFilter 中,该方法代码如下:
public class SessionInitializerFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
((HttpServletRequest)request).getSession();
chain.doFilter(request, response);
}
}
在 doFilter() 方法中,调用了FilterChain.doFilter() 方法。从前面已知FilterChain.doFilter() 方法会取出下标 pos 处的 Filter,而在调用当前 Filter 对象的 doFilter() 方法之前,应执行了 pos++ 方法,把 pos 值加1,所以这里会取出数组中下一个 Filter,从而实现了请求转发。
因此,每个Filter实现类在处理完自己的逻辑后,通过调用 FilterChain的方法来把请求转发给下一个过滤器。整个过程和时序图如下图所示:
前面展示了 指针、数组 形式的“责任链”实现方式,实际上“责任链”的实现方式是很多样的,需要根据业务需求选择一个合适的方式来实现,比如 Netty 的 ChannelPipeline 使用了一个双向链表实现了ChannelHandler 的责任链。
优点和缺点
- 请求的提交者(用户、客户端)并不知道最终是由哪个对象处理他的请求,只知道他的请求会被处理,这降低了请求与处理对象之间的耦合;
- 我们可以根据业务需要,动态地增加、删减责任链上的处理对象,而不会影响到已有的代码,增强了指派职责的灵活;
总结
对同一个请求要连续做多种不同的处理时,可以使用责任链模式。
和“策略模式”很相似,都能实现“请求与处理对象解耦”,但“责任链模式”可以依次执行多个方法,“策略模式”是在多个方法中选择一个。