责任链模式了解一下

2021-08-18 10:40:38 浏览数 (1)

责任链模式定义

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

责任链模式的demo

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

类图

首先我们画好一个类图

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

代码实现

代码语言:javascript复制
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类,其他的两个处理器类也类似。

代码语言:javascript复制
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

代码语言:javascript复制
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, "控制台信息");

    }
}

运行结果如下:

通过lambda来实现

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

代码语言:javascript复制
@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<String> 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.降低耦合度,它将请求的发送者和接收者解耦,请求只管发送,不管谁来处理。

1.简化了对象,使得对象不需要知道链的结构2.增强给对象指派的灵活性,通过改变链内的成员或者调动他们的次序,允许动态地新增或删除责任3.增加新的请求处理类很方便

缺点

4.不能保证请求一定被接收5.系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用

应用场景

6.有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。7.在不明确指定接收者的情况下,向多个对象中的一个提交一个请求8.可动态指定一组对象处理请求

小结

前面介绍了责任链模式的定义,优缺点,应用场景,并且实现了一个责任链的demo。

那么在实际的开发中,我们有没有碰到责任链模式呢?

答案是有的,请看下面。

Servlet中运用Filter

不管是用SpringMVC还是用SpringBoot,我们都绕不开过滤器Filter,同时,我们也会自定义一些过滤器,例如:

权限过滤器,字符过滤器。

不管是何种过滤器,我们都需要实现过滤器接口Filter,并且重写doFilter方法

代码语言:javascript复制
public interface Filter {
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException;
            }

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

代码语言:javascript复制
@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模式调试我们看到如下调用栈。如下图所示:

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

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

代码语言:javascript复制
public final class ApplicationFilterChain implements FilterChain {
 /**
     * Filters.
     */
        private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
}

定义了一个初始大小为0的ApplicationFilterConfig数组,那么ApplicationFilterConfig是个啥呢?

代码语言:javascript复制
/**
 * Implementation of a <code>javax.servlet.FilterConfig</code> 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 <code>FilterDef</code> that defines our associated Filter.
     */
    private final FilterDef filterDef;
}

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

代码语言:javascript复制
/**
     * 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方法中。

代码语言:javascript复制
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方法。

代码语言:javascript复制
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方法中。

代码语言:javascript复制
    /**
     * 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和Filter2.将具体处理器初始化到链条中,并做抽象方法的具体实现,如自定义过滤器XssFilter3.具体处理器之间的引用和处理条件的判断4.设计链条结束标识,如通过pos游标。实际的应用中我们可以将责任链模式应用到流程审批(例如:请假的审批)的过程中,因为每个审批人都可以看成一个具体的处理器,上一个审批人审批完成之后需要将请求转给下一个审批人,直到审批完成。同时需要注意的时候,如果请求链过长, 则可能会非常的影响系统性能,也要防止循环调用的情况。

参考

什么是责任链设计模式? 责任链模式[1]

References

[1] 责任链模式: https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html

0 人点赞