Java 设计模式——责任链模式

在阎宏博士的《JAVA 与模式》一书中开头是这样描述责任链(Chain of Responsibility)模式的:

责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。

从击鼓传花谈起

击鼓传花是一种热闹而又紧张的饮酒游戏。在酒宴上宾客依次坐定位置,由一人击鼓,击鼓的地方与传花的地方是分开的,以示公正。开始击鼓时,花束就开始依次传递,鼓声一落,如果花束在某人手中,则该人就得饮酒。

比如说,贾母、贾赦、贾政、贾宝玉和贾环是五个参加击鼓传花游戏的传花者,他们组成一个环链。击鼓者将花传给贾母,开始传花游戏。花由贾母传给贾赦,由贾赦传给贾政,由贾政传给贾宝玉,又贾宝玉传给贾环,由贾环传回给贾母,如此往复,如下图所示。当鼓声停止时,手中有花的人就得执行酒令。
670a3e2a13fd4d90a1f83ac084821875.png
击鼓传花便是责任链模式的应用。责任链可能是一条直线、一个环链或者一个树结构的一部分。

责任链模式的结构

下面使用了一个责任链模式的最简单的实现。
1557f17890b6464bb83218e6747b9337.png

责任链模式涉及到的角色如下所示:

  • 抽象处理者 (Handler) 角色: 定义出一个处理请求的接口。如果需要,接口可以定义 出一个方法以设定和返回对下家的引用。这个角色通常由一个 Java 抽象类或者 Java 接口实现。上图中 Handler 类的聚合关系给出了具体子类对下家的引用,抽象方法 handleRequest() 规范了子类处理请求的操作。

  • 具体处理者 (ConcreteHandler) 角色: 具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。

源代码

抽象处理者角色

c4495a3f04334e4594479b5a40708260.png

具体处理者角色

f085b7c4c7b24592a7419050bce86385.png

客户端类

ffbb549cde5f4fca8a891c14bfe644f7.png

可以看出,客户端创建了两个处理者对象,并指定第一个处理者对象的下家是第二个处理者对象,而第二个处理者对象没有下家。然后客户端将请求传递给第一个处理者对象。

由于本示例的传递逻辑非常简单:只要有下家,就传给下家处理;如果没有下家,就自行处理。因此,第一个处理者对象接到请求后,会将请求传递给第二个处理者对象。由于第二个处理者对象没有下家,于是自行处理请求。活动时序图如下所示:
132eb5275b1c47c08d1cfa27e970bca3.png

使用场景

来考虑这样一个功能: 申请聚餐费用的管理。

很多公司都是这样的福利,就是项目组或者是部门可以向公司申请一些聚餐费用,用于组织项目组成员或者是部门成员进行聚餐活动。

申请聚餐费用的大致流程一般是:由申请人先填写申请单,然后交给领导审批,如果申请批准下来,领导会通知申请人审批通过,然后申请人去财务领取费用,如果没有批准下来,领导会通知申请人审批未通过,此事也就此作罢。

不同级别的领导,对于审批的额度是不一样的,比如,项目经理只能审批 500 元以内的申请;部门经理能审批 1000 元以内的申请;而总经理可以审核任意额度的申请。

也就是说,当某人提出聚餐费用申请的请求后,该请求会经由项目经理、部门经理、总经理之中的某一位领导来进行相应的处理,但是提出申请的人并不知道最终会由谁来处理他的请求,一般申请人是把自己的申请提交给项目经理,或许最后是由总经理来处理他的请求。

可以使用责任链模式来实现上述功能:当某人提出聚餐费用申请的请求后,该请求会在 项目经理–> 部门经理–> 总经理这样一条领导处理链上进行传递,发出请求的人并不知道谁会来处理他的请求,每个领导会根据自己的职责范围,来判断是处理请求还是把请求交给更高级别的领导,只要有领导处理了,传递就结束了。
需要把每位领导的处理独立出来,实现成单独的职责处理对象,然后为它们提供一个公共的、抽象的父职责对象,这样就可以在客户端来动态地组合职责链,实现不同的功能要求了。
31c2a41faa8046f6891f360cfe5187e1.png

源代码

抽象处理者角色类

4c4d50f26242498f99380e3a034c274b.png

具体处理者角色

bcd9275e72db42fd84dc2e6d157e99b2.png
ebe13c2b2d5e41f8a913894aae6a9a8d.png
0018fbcb998d4880b3ca77371761da24.png

客户端类

193dcecc44b646a498c45d23c1b45e32.png
7772a1abdaa04356bccafc378d4f8f2e.png

运行结果

f9898cc1ce824ebbbf555de3f1e8f259.png

纯的与不纯的责任链模式

一个纯的责任链模式要求一个具体的处理者对象只能在两个行为中选择一个:一是承担责任,而是把责任推给下家。不允许出现某一个具体处理者对象在承担了一部分责任后又 把责任向下传的情况。

在一个纯的责任链模式里面,一个请求必须被某一个处理者对象所接收;在一个不纯的责任链模式里面,一个请求可以最终不被任何接收端对象所接收。

纯的责任链模式的实际例子很难找到,一般看到的例子均是不纯的责任链模式的实现。有些人认为不纯的责任链根本不是责任链模式,这也许是有道理的。但是在实际的系统里,纯的责任链很难找到。如果坚持责任链不纯便不是责任链模式,那么责任链模式便不会有太大意义了。

责任链模式在 Tomcat 中的应用

众所周知 Tomcat 中的 Filter 就是使用了责任链模式,创建一个 Filter 除了要在 web.xml 文件中做相应配置外,还需要实现 javax.servlet.Filter 接口。



public class TestFilter implements Filter{

public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {
    
    chain.doFilter(request, response);
}

public void destroy() {
}

public void init(FilterConfig filterConfig) throws ServletException {
}

}

使用 DEBUG 模式所看到的结果如下 ![95a56219c36e48e7b81330674fde194d.png](http://pe7ya36zj.yz1618.cn/file/2018/11/95a56219c36e48e7b81330674fde194d.png)

其实在真正执行到 TestFilter 类之前,会经过很多 Tomcat 内部的类。顺带提一下其实 Tomcat 的容器设置也是责任链模式,注意被红色方框所圈中的类,从 Engine 到 Host 再到 Context 一直到 Wrapper 都是通过一个链传递请求。被绿色方框所圈中的地方有一个名为 ApplicationFilterChain 的类,ApplicationFilterChain 类所扮演的就是抽象处理者角色,而具体处理者角色由各个 Filter 扮演。

第一个疑问是 ApplicationFilterChain 将所有的 Filter 存放在哪里?

答案是保存在 ApplicationFilterChain 类中的一个 ApplicationFilterConfig 对象的数组中。



/**
* Filters.
*/
private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

那 ApplicationFilterConfig 对象又是什么呢?

ApplicationFilterConfig 是一个 Filter 容器。以下是 ApplicationFilterConfig 类的声明:



/**
* Implementation of a javax.servlet.FilterConfig useful in
* managing the filter instances instantiated when a web application
* is first started.
*
* @author Craig R. McClanahan
* @version $Id: ApplicationFilterConfig.java 1201569 2011-11-14 01:36:07Z kkolinko $
*/

当一个 web 应用首次启动时 ApplicationFilterConfig 会自动实例化,它会从该 web 应用的 web.xml 文件中读取配置的 Filter 的信息,然后装进该容器。

刚刚看到在 ApplicationFilterChain 类中所创建的 ApplicationFilterConfig 数组长度为零,那它是在什么时候被重新赋值的呢?



private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

是在调用 ApplicationFilterChain 类的 addFilter() 方法时。

/**
     * The int which gives the current number of filters in the chain.
     */
    private int n = 0;
    public static final int INCREMENT = 10;
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;

}

变量 n 用来记录当前过滤器链里面拥有的过滤器数目,默认情况下 n 等于 0,ApplicationFilterConfig 对象数组的长度也等于 0,所以当第一次调用 addFilter()方法时,if (n == filters.length) 的条件成立,ApplicationFilterConfig 数组长度被改变。之后 filters[n++] = filterConfig; 将变量 filterConfig 放入 ApplicationFilterConfig 数组中并将当前过滤器链里面拥有的过滤器数目 +1。

那 ApplicationFilterChain 的 addFilter() 方法又是在什么地方被调用的呢?

是在 ApplicationFilterFactory 类的 createFilterChain() 方法中。



public ApplicationFilterChain createFilterChain (ServletRequest request, Wrapper wrapper, Servlet servlet) {

    // get the dispatcher type
    DispatcherType dispatcher = null; 
    if (request.getAttribute(DISPATCHER_TYPE_ATTR) != null) {
        dispatcher = (DispatcherType) request.getAttribute(DISPATCHER_TYPE_ATTR);
    }
    String requestPath = null;
    Object attribute = request.getAttribute(DISPATCHER_REQUEST_PATH_ATTR);
    
    if (attribute != null){
        requestPath = attribute.toString();
    }
    
    // If there is no servlet to execute, return null
    if (servlet == null)
        return (null);

    boolean comet = false;
    
    // Create and initialize a filter chain object
    ApplicationFilterChain filterChain = null;
    if (request instanceof Request) {
        Request req = (Request) request;
        comet = req.isComet();
        if (Globals.IS_SECURITY_ENABLED) {
            // Security: Do not recycle
            filterChain = new ApplicationFilterChain();
            if (comet) {
                req.setFilterChain(filterChain);
            }
        } else {
            filterChain = (ApplicationFilterChain) req.getFilterChain();
            if (filterChain == null) {
                filterChain = new ApplicationFilterChain();
                req.setFilterChain(filterChain);
            }
        }
    } else {
        // Request dispatcher in use
        filterChain = new ApplicationFilterChain();
    }

    filterChain.setServlet(servlet);

    filterChain.setSupport
        (((StandardWrapper)wrapper).getInstanceSupport());

    // Acquire the filter mappings for this Context
    StandardContext context = (StandardContext) wrapper.getParent();
    FilterMap filterMaps[] = context.findFilterMaps();

    // If there are no filter mappings, we are done
    if ((filterMaps == null) || (filterMaps.length == 0))
        return (filterChain);

    // Acquire the information we will need to match filter mappings
    String servletName = wrapper.getName();

    // Add the relevant path-mapped filters to this filter chain
    for (int i = 0; i < filterMaps.length; i++) {
        if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
            continue;
        }
        if (!matchFiltersURL(filterMaps[i], requestPath))
            continue;
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
            context.findFilterConfig(filterMaps[i].getFilterName());
        if (filterConfig == null) {
            // FIXME - log configuration problem
            continue;
        }
        boolean isCometFilter = false;
        if (comet) {
            try {
                isCometFilter = filterConfig.getFilter() instanceof CometFilter;
            } catch (Exception e) {
                // Note: The try catch is there because getFilter has a lot of 
                // declared exceptions. However, the filter is allocated much
                // earlier
                Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(t);
            }
            if (isCometFilter) {
                filterChain.addFilter(filterConfig);
            }
        } else {
            filterChain.addFilter(filterConfig);
        }
    }

    // Add filters that match on servlet name second
    for (int i = 0; i < filterMaps.length; i++) {
        if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
            continue;
        }
        if (!matchFiltersServlet(filterMaps[i], servletName))
            continue;
        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
            context.findFilterConfig(filterMaps[i].getFilterName());
        if (filterConfig == null) {
            // FIXME - log configuration problem
            continue;
        }
        boolean isCometFilter = false;
        if (comet) {
            try {
                isCometFilter = filterConfig.getFilter() instanceof CometFilter;
            } catch (Exception e) {
                // Note: The try catch is there because getFilter has a lot of 
                // declared exceptions. However, the filter is allocated much
                // earlier
            }
            if (isCometFilter) {
                filterChain.addFilter(filterConfig);
            }
        } else {
            filterChain.addFilter(filterConfig);
        }
    }

    // Return the completed filter chain
    return (filterChain);

}

可以将如上代码分为两段,51 行之前为第一段,51 行之后为第二段。

第一段的主要目的是创建 ApplicationFilterChain 对象以及一些参数设置。

第二段的主要目的是从上下文中获取所有 Filter 信息,之后使用 for 循环遍历并调用 filterChain.addFilter(filterConfig); 将 filterConfig 放入 ApplicationFilterChain 对象的 ApplicationFilterConfig 数组中。

那 ApplicationFilterFactory 类的 createFilterChain() 方法又是在什么地方被调用的呢?

是在 StandardWrapperValue 类的 invoke() 方法中被调用的。

b4e84c6aed1148faabd0619a2e7f7f20.png

由于 invoke() 方法较长,所以将很多地方省略。



public final void invoke(Request request, Response response)
throws IOException, ServletException {
…省略中间代码
     // Create the filter chain for this request
ApplicationFilterFactory factory =
ApplicationFilterFactory.getInstance();
ApplicationFilterChain filterChain =
factory.createFilterChain(request, wrapper, servlet);
  …省略中间代码
filterChain.doFilter(request.getRequest(), response.getResponse());
  …省略中间代码
}

那正常的流程应该是这样的:

在 StandardWrapperValue 类的 invoke()方法中调用 ApplicationFilterChai 类的 createFilterChain() 方法———> 在 ApplicationFilterChai 类的 createFilterChain()方法中调用 ApplicationFilterChain 类的 addFilter() 方法———> 在 ApplicationFilterChain 类的 addFilter() 方法中给 ApplicationFilterConfig 数组赋值。
2190e6e3866d416ba87f095085cfa78a.png
根据上面的代码可以看出 StandardWrapperValue 类的 invoke()方法在执行完 createFilterChain() 方法后,会继续执行 ApplicationFilterChain 类的 doFilter()方法,然后在 doFilter() 方法中会调用 internalDoFilter() 方法。

以下是 internalDoFilter() 方法的部分代码:



// Call the next filter if there is one
if (pos < n) {
       //拿到下一个Filter,将指针向下移动一位
//pos它来标识当前ApplicationFilterChain(当前过滤器链)执行到哪个过滤器
ApplicationFilterConfig filterConfig = filters[pos++];
Filter filter = null;
try {
          //获取当前指向的Filter的实例
filter = filterConfig.getFilter();
support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
filter, request, response);

            if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                    filterConfig.getFilterDef().getAsyncSupported())) {
                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                        Boolean.FALSE);
            }
            if( Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal = 
                    ((HttpServletRequest) req).getUserPrincipal();

                Object[] args = new Object[]{req, res, this};
                SecurityUtil.doAsPrivilege
                    ("doFilter", filter, classType, args, principal);
                
            } else {

            //调用Filter的doFilter()方法
filter.doFilter(request, response, this);
}

这里的 filter.doFilter(request, response, this); 就是调用我们前面创建的 TestFilter 中的 doFilter()方法。而 TestFilter 中的 doFilter() 方法会继续调用 chain.doFilter(request, response); 方法,而这个 chain 其实就是 ApplicationFilterChain, 所以调用过程又回到了上面调用 dofilter 和调用 internalDoFilter 方法,这样执行直到里面的过滤器全部执行。 如果定义两个过滤器,则 Debug 结果如下: ![bce84a8b53a94df5bdc404c09b78ad41.png](http://pe7ya36zj.yz1618.cn/file/2018/11/bce84a8b53a94df5bdc404c09b78ad41.png)

使用场景

有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定,客户端只需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的。

在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。

可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序。

总结

职责链模式优点

  • 职责链模式使得一个对象无须知道是其他哪一个对象处理其请求,对象仅需知道该请求会被处理即可,接收者和发送者都没有对方的明确信息,且链中的对象不需要知道链的结构,由客户端负责链的创建,降低了系统的耦合度。

  • 请求处理对象仅需维持一个指向其后继者的引用,而不需要维持它对所有的候选处理者的引用,可简化对象的相互连接。

  • 在给对象分派职责时,职责链可以给我们更多的灵活性,可以通过在运行时对该链进行动态的增加或修改来处理一个请求的职责。

  • 在系统中增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可,从这一点来看是符合“开闭原则”的。

职责链模式缺点

  • 因为一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直到链的末端都得不到处理;一个请求也可能因职责链没有被正确配置而得不到处理。

  • 对于比较长的职责链,请求的处理可能涉及到多个处理对象,系统性能将受到一定影响,而且在进行代码调试时不太方便。

  • 如果建链不当,可能会造成循环调用,将导致系统陷入死循环。