Fork me on GitHub

【趣味设计模式系列】之【责任链模式】

1. 简介

责任链模式(Chain of Responsibility):使多个对象都有机会处理请求,从而避免了请求的发送者接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

2. 图解

商城新开张,每个订单,可以享受多张优惠券叠加减免

责任链模式

3. 案例实现

类图如下

  • 定义一个优惠券打折抽象类;
  • 抽象类包含一个指向自身的引用nextDiscountFilter,用来把对象串成链,原价计算优惠后的价格方法calculateBySourcePrice;

实现类

  • FullDistcountFliter满200减20元;
  • FirstPurchaseDiscount首次购买减20元;
  • SecondPurchaseDiscountFilter第二件打9折;
  • HolidayDiscountFilter节日一律减5元.

接口与实现类

package com.wzj.chainOfResponsibility.example2;

/**
 * @Author: wzj
 * @Date: 2019/9/8 9:06
 * @Desc: 折扣优惠接口
 */
public abstract class DiscountFilter {

    // 下一个责任链成员
    protected DiscountFilter nextDiscountFilter;

    // 根据原价计算优惠后的价格
    public abstract int calculateBySourcePrice(int price);

}
package com.wzj.chainOfResponsibility.example2;

/**
 * @Author: wzj
 * @Date: 2019/9/8 9:07
 * @Desc: 满200减20元
 */
public class FullDiscountFilter extends DiscountFilter{

    public int calculateBySourcePrice(int price) {
        if (price > 200){
            System.out.println("优惠满减20元");
            price = price - 20;
        }

        if(this.nextDiscountFilter != null) {
            return super.nextDiscountFilter.calculateBySourcePrice(price);
        }
        return price;
    }
}

package com.wzj.chainOfResponsibility.example2;

/**
 * @Author: wzj
 * @Date: 2019/9/8 16:06
 * @Desc: 首次购买减20元
 */
public class FirstPurchaseDiscount extends DiscountFilter {
    public int calculateBySourcePrice(int price) {
        if (price > 100){
            System.out.println("首次购买减20元");
            price = price - 20;
        }

        if(this.nextDiscountFilter != null) {
            return super.nextDiscountFilter.calculateBySourcePrice(price);
        }
        return price;
    }
}

package com.wzj.chainOfResponsibility.example2;

/**
 * @Author: wzj
 * @Date: 2019/9/8 16:09
 * @Desc: 第二件打9折
 */
public class SecondPurchaseDiscountFilter  extends DiscountFilter{
    public int calculateBySourcePrice(int price) {

        System.out.println("第二件打9折");
        Double balance =  price * 0.9;

        if(this.nextDiscountFilter != null) {
            return super.nextDiscountFilter.calculateBySourcePrice(balance.intValue());
        }
        return price;
    }
}

package com.wzj.chainOfResponsibility.example2;

/**
 * @Author: wzj
 * @Date: 2019/9/8 16:02
 * @Desc: 节日一律减5元
 */
public class HolidayDiscountFilter extends DiscountFilter{
    public int calculateBySourcePrice(int price) {
        if (price > 20){
            System.out.println("节日一律减5元");
            price = price - 5;
        }

        if(this.nextDiscountFilter != null) {
            return super.nextDiscountFilter.calculateBySourcePrice(price);
        }
        return price;
    }
}

测试类

package com.wzj.chainOfResponsibility.example2;

/**
 * @Author: wzj
 * @Date: 2019/9/8 16:17
 * @Desc: 
 */
public class TestDiscountFilter {
    public static void main(String[] args) {
        int price = 240;
        String productStr = String.format("商品清单:苹果、香蕉、桔子, 商品总金额为:%d元.", price);
        System.out.println(productStr);
        //声明责任链上的所有节点
        FullDiscountFilter fulDF = new FullDiscountFilter();
        FirstPurchaseDiscount firstDF = new FirstPurchaseDiscount();
        SecondPurchaseDiscountFilter secDF = new SecondPurchaseDiscountFilter();
        HolidayDiscountFilter holDF = new HolidayDiscountFilter();
        //设置链中的顺序:满减->首购减->第二件减->假日减
        fulDF.nextDiscountFilter = firstDF;
        firstDF.nextDiscountFilter = secDF;
        secDF.nextDiscountFilter = holDF;
        holDF.nextDiscountFilter = null;
        int total = fulDF.calculateBySourcePrice(price);
        System.out.println(String.format("所有商品优惠价后金额为:%d", total));
    }


}

执行结果

商品清单:苹果、香蕉、桔子, 商品总金额为:240元.
优惠满减20元
首次购买减20元
第二件打9折
节日一律减5元
所有商品优惠价后金额为:175

4. 应用责任链模式手写过滤器

现在有这样一个场景,程序员张三在某相亲节目中找对象,在第一关和第二关分别被女孩灭灯pass掉,下面分别看看俩姑娘是如何过滤张三的。
小美对话张三

小静对话张三

此场景可以模拟一个http请求如何经过过滤器到服务端,经过每一个过滤器,可以想象为张三相亲过程中经过某个关卡,由于不符合该过滤器的条件被姑娘过滤掉。

第一个版本v1

  • 定义一个请求类和一个响应类MyRequestMyResponse
  • 定义过滤器MyFilter
  • 定义两个过滤器的实现类HeightFliterEducationalBackGroundFilter,分别实现身高过滤和学历过滤;
  • 定义链条MyFilterChain,实现接口MyFilteradd方法实现往过滤链条里添加过滤对象;doFilter方法实现所有过滤对象的过滤操作;

实现代码如下

package com.wzj.chainOfResponsibility.example1.v1;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:14
 * @Desc: 请求
 */
public class MyRequest {

    String str;

}

package com.wzj.chainOfResponsibility.example1.v1;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:15
 * @Desc: 响应
 */
public class MyResponse {

    String str;

}


package com.wzj.chainOfResponsibility.example1.v1;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:00
 * @Desc: 模拟实现过滤器
 */
public interface MyFilter {

    void doFilter(MyRequest myRequest, MyResponse myResponse);

}



package com.wzj.chainOfResponsibility.example1.v1;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:36
 * @Desc: 责任链
 */
public class MyFilterChain implements MyFilter {

    List<MyFilter> list = new ArrayList<MyFilter>();

    public MyFilterChain add(MyFilter myFilter) {
        list.add(myFilter);
        return this;
    }

    public void doFilter(MyRequest myRequest, MyResponse myResponse) {
        for(MyFilter f : list ){
            f.doFilter(myRequest, myResponse);
        }
    }
}


package com.wzj.chainOfResponsibility.example1.v1;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:20
 * @Desc: 身高过滤器
 */
public class HeightFliter implements MyFilter {

    public void doFilter(MyRequest myRequest, MyResponse myResponse) {
        myRequest.str = myRequest.str.replace("170", "个子有点矮");
        myResponse.str += "【妹子挑剔,需要过滤身高】";
    }
}



package com.wzj.chainOfResponsibility.example1.v1;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:33
 * @Desc: 教育背景过滤器
 */
public class EducationalBackGroundFilter implements MyFilter {
    public void doFilter(MyRequest myRequest, MyResponse myResponse) {
        myRequest.str = myRequest.str.replace("学历大专", "学历不高");
        myResponse.str += "【妹子挑剔,需要过滤学历】";
    }
}


测试类

package com.wzj.chainOfResponsibility.example1.v1;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:42
 * @Desc: 直观的方式处理response,
 * 并将response的处理放在request的下面
 */
public class TestV1 {
    public static void main(String[] args) {
        MyRequest myRequest = new MyRequest();
        myRequest.str = "张三身高170,学历大专,跪求妹子给个机会认识";
        System.out.println("request:" + myRequest.str);
        MyResponse myResponse = new MyResponse();
        myResponse.str = "";
        MyFilterChain chain = new MyFilterChain();
        chain.add(new HeightFliter()).add(new EducationalBackGroundFilter());
        chain.doFilter(myRequest, myResponse);
        System.out.println("response:" + myResponse.str);
    }

}


结果

request:张三身高170,学历大专,跪求妹子给个机会认识
response:【妹子挑剔,需要过滤身高】【妹子挑剔,需要过滤学历】

现在有如下需求,为更好的模拟一次完整请求,在过滤请求时顺序,响应请求时逆序,有何办法可以做到呢?

第二个版本v2

  • MyRequest类和MyResponse同v1
  • MyFilterChain中加入记录具体责任链对象的下标index,实现记录位置功能;
  • doFilter方法里面实现递归调用,并把当前的链条MyFilterChain作为参数一直向后传递;
    具体实现如下
package com.wzj.chainOfResponsibility.example1.v2;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:00
 * @Desc: 模拟实现过滤器
 */
public interface MyFilter {

    void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain);

}

package com.wzj.chainOfResponsibility.example1.v2;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:36
 * @Desc: 在MyFilterChain中处理加入位置的记录index
 */
public class MyFilterChain implements MyFilter {

    List<MyFilter> list = new ArrayList<MyFilter>();

    int index = 0;

    public MyFilterChain add(MyFilter myFilter) {
        list.add(myFilter);
        return this;
    }

    public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) {
        if(index == list.size())
            return;
        MyFilter myFilter = list.get(index);
        index ++;
        myFilter.doFilter(myRequest, myResponse, myFilterChain);
    }
}

package com.wzj.chainOfResponsibility.example1.v2;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:20
 * @Desc: 身高过滤器
 */
public class HeightFliter implements MyFilter {

    public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) {
        myRequest.str = myRequest.str.replace("170", "个子有点矮");
        myFilterChain.doFilter(myRequest, myResponse, myFilterChain);
        myResponse.str += "【妹子挑剔,需要过滤身高】";
    }
}

package com.wzj.chainOfResponsibility.example1.v2;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:33
 * @Desc: 教育背景过滤器
 */
public class EducationalBackGroundFilter implements MyFilter {
    public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) {
        myRequest.str = myRequest.str.replace("学历大专", "学历不高");
        myFilterChain.doFilter(myRequest, myResponse, myFilterChain);
        myResponse.str += "【妹子挑剔,需要过滤学历】";
    }
}

测试类

package com.wzj.chainOfResponsibility.example1.v2;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:42
 * @Desc: 过滤请求时顺序,响应请求时逆序,在MyFilterChain中处理加入位置的记录,
 * 同时在MyFilter中加入第三个参数MyFilterChain,让链条递归实现倒序
 */
public class TestV2 {
    public static void main(String[] args) {
        MyRequest myRequest = new MyRequest();
        myRequest.str = "张三身高170,学历大专,跪求妹子给个机会认识";
        System.out.println("request:" + myRequest.str);
        MyResponse myResponse = new MyResponse();
        myResponse.str = "";
        MyFilterChain chain = new MyFilterChain();
        chain.add(new HeightFliter()).add(new EducationalBackGroundFilter());
        chain.doFilter(myRequest, myResponse, chain);
        System.out.println("response:" + myResponse.str);
    }

}

结果中显示先过滤学历,后过滤身高。

request:张三身高170,学历大专,跪求妹子给个机会认识
response:【妹子挑剔,需要过滤学历】【妹子挑剔,需要过滤身高】

由于MyFilterChain在做doFilter时,始终传递的是当前MyFilterChain的同一个实例,故可以简化MyFilterChain

第三个版本v3

  • MyFilterChain去除MyFilter接口;
  • doFilter方法去除第三个参数MyFilterChain
    具体实现如下
package com.wzj.chainOfResponsibility.example1.v3;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:36
 * @Desc: 过滤器完全模式,去掉实现接口,并将doFilter方法中的chain
 */
public class MyFilterChain{

    List<MyFilter> list = new ArrayList<MyFilter>();

    int index = 0;

    public MyFilterChain add(MyFilter myFilter) {
        list.add(myFilter);
        return this;
    }

    public void doFilter(MyRequest myRequest, MyResponse myResponse) {
        if(index == list.size())
            return;
        MyFilter myFilter = list.get(index);
        index ++;
        myFilter.doFilter(myRequest, myResponse, this);
    }
}

package com.wzj.chainOfResponsibility.example1.v3;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:20
 * @Desc: 身高过滤器
 */
public class HeightFliter implements MyFilter {

    public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) {
        myRequest.str = myRequest.str.replace("170", "个子有点矮");
        myFilterChain.doFilter(myRequest, myResponse);
        myResponse.str += "【妹子挑剔,需要过滤身高】";
    }
}

package com.wzj.chainOfResponsibility.example1.v3;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:33
 * @Desc: 教育背景过滤器
 */
public class EducationalBackGroundFilter implements MyFilter {
    public void doFilter(MyRequest myRequest, MyResponse myResponse, MyFilterChain myFilterChain) {
        myRequest.str = myRequest.str.replace("学历大专", "学历不高");
        myFilterChain.doFilter(myRequest, myResponse);
        myResponse.str += "【妹子挑剔,需要过滤学历】";
    }
}

测试类

package com.wzj.chainOfResponsibility.example1.v3;

/**
 * @Author: wzj
 * @Date: 2019/9/7 20:42
 * @Desc: 过滤器完全模式
 */
public class TestV3 {
    public static void main(String[] args) {
        MyRequest myRequest = new MyRequest();
        myRequest.str = "张三身高170,学历大专,跪求妹子给个机会认识";
        System.out.println("request:" + myRequest.str);
        MyResponse myResponse = new MyResponse();
        myResponse.str = "";
        MyFilterChain chain = new MyFilterChain();
        chain.add(new HeightFliter()).add(new EducationalBackGroundFilter());
        chain.doFilter(myRequest, myResponse);
        System.out.println("response:" + myResponse.str);
    }

}

结果

request:张三身高170,学历大专,跪求妹子给个机会认识
response:【妹子挑剔,需要过滤学历】【妹子挑剔,需要过滤身高】

5. 过滤器源码分析

在web项目中经常需要配置满足我们需要的各种过滤器filter,来过滤满足我们自定义信息,不烦看一下tomcat中的源码中是如何做到的,下面以tomcat8.5.37的源码来分析具体实现。
找到ApplicationFilterFactory类,其中的createFilterChain(ServletRequest request, Wrapper wrapper, Servlet servlet)方法创建了责任链,

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

        // 如何没有servlet执行,返回null
        if (servlet == null)
            return null;

        // 创建和初始化过滤器链对象
        ApplicationFilterChain filterChain = null;
        if (request instanceof Request) {
            Request req = (Request) request;
            if (Globals.IS_SECURITY_ENABLED) {
                filterChain = new ApplicationFilterChain();
            } else {
                filterChain = (ApplicationFilterChain) req.getFilterChain();
                if (filterChain == null) {
                    filterChain = new ApplicationFilterChain();
                    req.setFilterChain(filterChain);
                }
            }
        } else {
         
            filterChain = new ApplicationFilterChain();
        }

        filterChain.setServlet(servlet);
        filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());

        // 获取上下中的过滤器的映射集合
        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);

        // 获取匹配的过滤器映射信息
        DispatcherType dispatcher =
                (DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);

        String requestPath = null;
        Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);
        if (attribute != null){
            requestPath = attribute.toString();
        }

        String servletName = wrapper.getName();

        // 对过滤器链添加对应的路径匹配过滤器
        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;
            }
            filterChain.addFilter(filterConfig);
        }

        // 添加匹配servlet name的过滤器
        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;
            }
            filterChain.addFilter(filterConfig);
        }

        // 返回过滤器责任链
        return filterChain;
    }

执行过滤器中的doFilter方法,会调用一个 internalDoFilter() 方法

public final class ApplicationFilterChain implements FilterChain {


......


     /**
     * 维护当前过滤器链的位置信息
     */
    private int pos = 0;


    /**
     * 当前过滤器的长度
     */
    private int n = 0;


......

@Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {

        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedExceptionAction<Void>() {
                        @Override
                        public Void run()
                            throws ServletException, IOException {
                            internalDoFilter(req,res);
                            return null;
                        }
                    }
                );
            } catch( PrivilegedActionException pe) {
                Exception e = pe.getException();
                if (e instanceof ServletException)
                    throw (ServletException) e;
                else if (e instanceof IOException)
                    throw (IOException) e;
                else if (e instanceof RuntimeException)
                    throw (RuntimeException) e;
                else
                    throw new ServletException(e.getMessage(), e);
            }
        } else {
            internalDoFilter(request,response);
        }
    }

    private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {

        // 如果有下一个,则调用下一个过滤器
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                Filter filter = filterConfig.getFilter();

                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()方法 , doFilter 又会调用 internalDoFilter,一直递归下去, 直到调用完所有的过滤器
                    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;
        }

        // 从最后一个过滤器开始调用
        try {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(request);
                lastServicedResponse.set(response);
            }

            if (request.isAsyncSupported() && !servletSupportsAsync) {
                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                        Boolean.FALSE);
            }
            // Use potentially wrapped request from this point
            if ((request instanceof HttpServletRequest) &&
                    (response instanceof HttpServletResponse) &&
                    Globals.IS_SECURITY_ENABLED ) {
                final ServletRequest req = request;
                final ServletResponse res = response;
                Principal principal =
                    ((HttpServletRequest) req).getUserPrincipal();
                Object[] args = new Object[]{req, res};
                SecurityUtil.doAsPrivilege("service",
                                           servlet,
                                           classTypeUsedInService,
                                           args,
                                           principal);
            } else {
                servlet.service(request, response);
            }
        } catch (IOException | ServletException | RuntimeException e) {
            throw e;
        } catch (Throwable e) {
            e = ExceptionUtils.unwrapInvocationTargetException(e);
            ExceptionUtils.handleThrowable(e);
            throw new ServletException(sm.getString("filterChain.servlet"), e);
        } finally {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(null);
                lastServicedResponse.set(null);
            }
        }
    }

}

6. 责任链模式总结

优点

  • 将请求发送者和接收处理者分开。请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌,两者解耦,提高系统的灵活性。

缺点

  • 性能问题,每个请求都是从链头遍历到链尾,特别是在链比较长的时候,性能是一个非常大的问题;
  • 调试不很方便,特别是链条比较长,类似递归的方式,调试的时候逻辑可能比较复杂。

注意事项

  • 链中节点数量需要控制,避免出现超长链的情况,一般的做法是在Handler中设置一个最大节点数量,在setNext方法中判断是否已经是超过其阈值,超过则不允许该链建立,避免无意识地破坏系统性能。
posted @ 2019-09-22 23:21  小猪爸爸  阅读(1335)  评论(0编辑  收藏  举报