前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >责任链模式了解一下

责任链模式了解一下

作者头像
码农飞哥
发布2021-08-18 10:40:38
3090
发布2021-08-18 10:40:38
举报
文章被收录于专栏:好好学习

责任链模式定义

责任链模式(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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-02-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农飞哥 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 责任链模式定义
  • 责任链模式的demo
    • 类图
      • 代码实现
        • 通过lambda来实现
        • 优缺点
          • 优点
            • 缺点
            • 应用场景
            • 小结
            • Servlet中运用Filter
            • 总结
            • 参考
              • References
              相关产品与服务
              日志服务
              日志服务(Cloud Log Service,CLS)是腾讯云提供的一站式日志服务平台,提供了从日志采集、日志存储到日志检索,图表分析、监控告警、日志投递等多项服务,协助用户通过日志来解决业务运维、服务监控、日志审计等场景问题。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档