设计模式学习12----责任链模式

简介: 责任链模式(Chain of Responsibilty Pattern)避免请求发送者与接收者耦合在一起,让多个对象处理器都有可能接收请求,将这些对象处理器连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。责任链模式是属于行为型模式。责任链模式的核心就是设计好一个请求链以及链结束的标识。

责任链模式定义

责任链模式(Chain of Responsibilty Pattern)避免请求发送者与接收者耦合在一起,让多个对象处理器都有可能接收请求,将这些对象处理器连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。责任链模式是属于行为型模式。责任链模式的核心就是设计好一个请求链以及链结束的标识。

下面先看一个责任链的demo。

责任链模式的demo

日志框架中,日志按照级别分为,控制台日志(DEBUG), 文件日志(INFO)以及错误日志 (ERROR)。每个级别的日志需要在其本级和下级打印,例如:ERROR级别的日志可以在控制台和日志文件中输出。这个需求我们就可以选用责任链模式来实现。

类图

首先我们画好一个类图

如上图,日志抽象类AbstractLogger主要是定义一些公共的方法逻辑,定义了每个子类需要实现的抽象方法,以及每个子类的下一个处理器。ConsoleLogger,FileLogger和ErrorLogger则是三个具体的处理器。其级别分别对应DEBUG,INFO和ERROR级别,每个具体处理器都实现了write方法。

接着我们来看看上面类图的具体代码实现。首先来看看AbstractLogger。

代码实现

public abstract class AbstractLogger {
    protected static int DEBUG = 1;
    protected static int INFO = 2;
    protected static int ERROR = 3;
    protected int level;
    protected AbstractLogger nextLogger;
  //设置下一个处理器的方法
    public void setNextLogger(AbstractLogger nextLogger) {
        this.nextLogger = nextLogger;
    }
  //公共的输出日志的方法
    public void logMessage(int level, String message) {
        if (this.level <= level) {
            write(message);
        }
        if (nextLogger != null) {
            nextLogger.logMessage(level, message);
        }
    }
  //抽象方法,每个具体的处理器都要重写
     protected abstract void write(String message);
}

然后就是三个具体的处理器,篇幅限制,在此只列举一个ConsoleLogger类,其他的两个处理器类也类似。

public class ConsoleLogger extends AbstractLogger {
    public ConsoleLogger() {
  //设置本处理器处理的日志级别
        this.level = DEBUG;
  //设置下一个处理器
        setNextLogger(new FileLogger());
    }
    @Override
    protected void write(String message) {
        System.out.println("**********Console logger:" + message);
    }
}

最后看看测试类ChainPatternDemo

public class ChainPatternDemo {
    public static void main(String[] args) {
        AbstractLogger consoleLogger = new ConsoleLogger();
        consoleLogger.logMessage(3, "错误信息");
        System.out.println();
        consoleLogger.logMessage(2, "文件信息");
        System.out.println();
        consoleLogger.logMessage(1, "控制台信息");
    }
}

运行结果如下:

77492911ecd7ba0da426db23a0f23d46_20200220135734260.jpg


通过lambda来实现

上面是通过常规的方式来实现的责任链模式,那么我们能不能通过lambda来实现类,通过lambda来实现的话,我们只需要定义一个函数接口,然后通过lambda来构造具体的实现类。代码如下:

@FunctionalInterface
public interface LambdaLogger {
    int DEBUG = 1;
    int INFO = 2;
    int ERROR = 3;
    /**
     * 函数式接口中唯一的抽象方法
     *
     * @param message
     */
     void message(String message,int level);
    default LambdaLogger appendNext(LambdaLogger nextLogger) {
        return (msg,level)->{
          //前面logger处理完才用当前logger处理
            message(msg, level);
            nextLogger.message(msg, level);
        };
    }
   static LambdaLogger logMessage(int level, Consumer writeMessage){
        //temLevel是日志处理器的级别,level是要打印的日志级别
       return (message, temLevel) -> {
           if (temLevel >= level) {
               writeMessage.accept(message);
           }
        };
    }
   //调试日志
    static LambdaLogger consoleLogMessage(int level1) {
       return logMessage(level1, (message) -> System.out.println("**********Console logger:" + message));
    }
  //ERROR日志
    static LambdaLogger errorLogMessage(int level) {
        return  logMessage(level, message -> System.out.println("***********Error logger: " + message));
    } 
  //INFO日志
    static LambdaLogger fileLogger(int level) {
        return logMessage(level, message -> System.out.println("*******File Logger: " + message));
    }
     static void main(String[] args) {
        LambdaLogger logger = consoleLogMessage(1).appendNext(fileLogger(2)).appendNext(errorLogMessage(3));
        //consoleLogger
        logger.message("控制台信息", 1);
        logger.message("文件信息", 2);
        logger.message("错误信息", 3);
    }
}

说完了责任链demo,接下来我们来总结下责任链模式的优缺点。


优缺点

优点

1.降低耦合度,它将请求的发送者和接收者解耦,请求只管发送,不管谁来处理。

2. 简化了对象,使得对象不需要知道链的结构

3. 增强给对象指派的灵活性,通过改变链内的成员或者调动他们的次序,允许动态地新增或删除责任

4. 增加新的请求处理类很方便


缺点

1.不能保证请求一定被接收

2.系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用


应用场景

有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。

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

3.可动态指定一组对象处理请求

小结

前面介绍了责任链模式的定义,优缺点,应用场景,并且实现了一个责任链的demo。那么在实际的开发中,我们有没有碰到责任链模式呢?答案是有的,请看下面。


Servlet中运用Filter

不管是用SpringMVC还是用SpringBoot,我们都绕不开过滤器Filter,同时,我们也会自定义一些过滤器,例如:权限过滤器,字符过滤器。不管是何种过滤器,我们都需要实现过滤器接口Filter,并且重写doFilter方法

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

如下:我们自定义了一个字符过滤器XssFilter,用来过滤字符,防止XSS注入。我们也是重写了doFilter方法,对黑名单中的字符进行过滤,对不在黑名单中的请求直接转给下一个过滤器。

@Component
@WebFilter(urlPatterns = "/*",filterName = "XssFilter")
public class XssFilter implements Filter {
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
  }
  @Override
  public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        //在黑名单中
        if(allUrl(url)){
        //走xxs过滤
        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper(
          (HttpServletRequest) request);
        chain.doFilter(xssRequest, response);
        }else{
        //走原先方法
         chain.doFilter(request, response);
        }
       }
  @Override
  public void destroy() {
  }
}

通过debug模式调试我们看到如下调用栈。如下图所示:

4b8b96d0e726677910d2e7e32b88a4ca_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTQ1MzQ4MDg=,size_16,color_FFFFFF,t_70.jpg

红框中标记的内容是Tomcat容器设置的责任链,从Engine到Context再到Wrapper都是通过责任链的方式来调用的。它们都是继承了ValueBase抽象类,实现了Value接口。


不过此处我们要重点关注下与我们过滤器XssFilter息息相关的ApplicationFilterChain类,这是一个过滤器的调用链的类。在该类中定义了一个ApplicationFilterConfig数组来保存所有需要调用的过滤器Filters。

public final class ApplicationFilterChain implements FilterChain {
 /**
     * Filters.
     */
     private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
}

定义了一个初始大小为0的ApplicationFilterConfig数组,那么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
 */
public final class ApplicationFilterConfig implements FilterConfig, Serializable {
  /**
     * The application Filter we are configured for.
     */
    private transient Filter filter = null;
   /**
     * The FilterDef that defines our associated Filter.
     */
    private final FilterDef filterDef;
}

ApplicationFilterConfig类是在web应用第一次启动的时候管理Filter的实例化的,其内部定义了一个Filter类型的全局变量。那么ApplicationFilterConfig数组的大小又是在啥时候被重新设置的呢?答案是在ApplicationFilterChain调用addFilter方法的时候,那么我们来看看addFilter方法

/**
     * The int which gives the current number of filters in the chain.
     *  当前链中过滤器的数量,默认是0
     */
    private int n = 0;
    /**
     * Add a filter to the set of filters that will be executed in this chain.
     *  将一个过滤器添加到过滤器链中
     * @param filterConfig The FilterConfig for the servlet to be executed
     */
 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 是用来记录当前链中过滤器的数量,默认为0,ApplicationFilterConfig对象数组的长度也等于0,所以应用第一次启动时if (n == filters.length)条件满足,添加完一个过滤器之后将n加1。那么addFilter方法是在什么时候调用的呢?答案就是在ApplicationFilterFactory类的createFilterChain方法中。

public final class ApplicationFilterFactory {
  public static ApplicationFilterChain createFilterChain(ServletRequest request,
            Wrapper wrapper, Servlet servlet) {
    ....... 省略部分代码
        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);
        }
    ...... 省略部分代码
  }
}

从名称来看ApplicationFilterFactory是一个工厂类,那么ApplicationFilterFactory类的createFilterChain方法又是何时候调用的呢?这就需要回到最顶端的StandardWrapperValve类的invoke方法。

final class StandardWrapperValve  extends ValveBase {
  @Override
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
    // Create the filter chain for this request
        ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
    ......
     filterChain.doFilter(request.getRequest(), response.getResponse());
  }
  }

最后我们就来看看 ApplicationFilterChain的doFilter方法。其内部的核心逻辑在internalDoFilter方法中。

/**
     * The int which is used to maintain the current position
     * in the filter chain.
     */
    private int pos = 0;
    private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {
   // Call the next filter if there is one
        if (pos < n) {
             ApplicationFilterConfig filterConfig = filters[pos++];
      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(request, response, this);
                }
    }
     servlet.service(request, response);
  }

pos 变量用来标记filterChain执行的当期位置,然后调用filter.doFilter(request, response, this)传递this(ApplicationFilterChain)进行链路传递,直至pos>n的时候(类似于击鼓传话中的鼓声停止),即所有的拦截器都执行完毕。


总结

应用责任链模式主要有如下几个步骤


1.设计一条链条和抽象处理方法 如Servlet的ApplicationFilterChain和Filter

2.将具体处理器初始化到链条中,并做抽象方法的具体实现,如自定义过滤器XssFilter

3.具体处理器之间的引用和处理条件的判断

4.设计链条结束标识,如通过pos游标。

实际的应用中我们可以将责任链模式应用到流程审批(例如:请假的审批)的过程中,因为每个审批人都可以看成一个具体的处理器,上一个审批人审批完成之后需要将请求转给下一个审批人,直到审批完成。

同时需要注意的时候,如果请求链过长, 则可能会非常的影响系统性能,也要防止循环调用的情况。

参考

什么是责任链设计模式?

责任链模式


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
1月前
|
设计模式 Java
【设计模式】责任链模式
【设计模式】责任链模式
|
3月前
|
设计模式
【设计模式】责任链模式
【1月更文挑战第27天】【设计模式】责任链模式
|
4月前
|
设计模式 存储 Java
认真学习设计模式之观察者模式(Observer Pattern)
认真学习设计模式之观察者模式(Observer Pattern)
29 0
|
3月前
|
设计模式 Dubbo Java
聊聊Java设计模式-责任链模式
责任链模式(Chain Of Responsibility Design Pattern),也叫做职责链,是将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
82 1
聊聊Java设计模式-责任链模式
|
5月前
|
设计模式 缓存 Java
认真学习设计模式之建造者模式(Builder Pattern)
认真学习设计模式之建造者模式(Builder Pattern)
59 1
|
3月前
|
设计模式 监控 安全
多线程设计模式【多线程上下文设计模式、Guarded Suspension 设计模式、 Latch 设计模式】(二)-全面详解(学习总结---从入门到深化)
多线程设计模式【多线程上下文设计模式、Guarded Suspension 设计模式、 Latch 设计模式】(二)-全面详解(学习总结---从入门到深化)
62 0
|
3月前
|
设计模式
设计模式之责任链模式
设计模式之责任链模式
|
1天前
|
设计模式 存储 前端开发
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
|
16天前
|
设计模式 PHP
php设计模式--责任链模式(五)
php设计模式--责任链模式(五)
13 0
|
4月前
|
设计模式 存储 Java
认真学习设计模式之命令模式(Command Pattern)
认真学习设计模式之命令模式(Command Pattern)
80 0