行为型设计模式之职责链模式

概述

设计模式是针对软件开发中经常遇到的一些设计问题,总结出来的一套解决方案或者设计思路。

大部分设计模式要解决的都是代码的可扩展性问题。

对于灵活多变的业务,需要用到设计模式,提升扩展性和可维护性,让代码能适应更多的变化;

设计模式的核心就是,封装变化,隔离可变性


设计模式解决的问题:

  • 创建型设计模式主要解决"对象的创建"问题,创建和使用代码解耦;
  • 结构型设计模式主要解决"类或对象的组合或组装"问题,将不同功能代码解耦;
  • 行为型设计模式主要解决的就是"类或对象之间的交互"问题。将不同的行为代码解耦,具体到观察者模式,它是将观察者和被观察者代码解耦。

设计模式关注重点: 了解它们都能解决哪些问题,掌握典型的应用场景,并且懂得不过度应用。

经典的设计模式有 23 种。随着编程语言的演进,一些设计模式(比如 Singleton)也随之过时,甚至成了反模式,一些则被内置在编程语言中(比如 Iterator),另外还有一些新的模式诞生(比如 Monostate)。

行为型

行为型设计模式比较多,有 11 个,几乎占了 23 种经典设计模式的一半。它们分别是:观察者模式、模板模式、策略模式、职责链模式、状态模式、迭代器模式、访问者模式、备忘录模式、命令模式、解释器模式、中介模式。

  • 常用的有:观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式。
  • 不常用的有:访问者模式、备忘录模式、命令模式、解释器模式、中介模式。

大部分设计模式都是提高代码的扩展性,很多都是利用Java的三大特性实现:封装、继承和多态,我们的需求要不要用设计模式首先看业务是不是多变的,需要提高扩展性吗,一个类对象的职责是否过多,是否需要解耦

职责链模式

模板模式、策略模式、职责链模式,这三种模式具有相同的作用:复用和扩展,在实际的项目开发中比较常用,特别是框架开发中,我们可以利用它们来提供框架的扩展点,能够让框架的使用者在不修改框架源码的情况下,基于扩展点定制化框架的功能。

定义:

将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。

在职责链模式中,多个处理器(也就是刚刚定义中说的"接收对象")依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。

代码实现:

两种比较常用的实现方式:一种是单链表结构、一种是数组结构

第一种实现方式如下所示。其中,Handler 是所有处理器类的抽象父类,handle() 是抽象方法。每个具体的处理器类(HandlerA、HandlerB)的 handle() 函数的代码结构类似,如果它能处理该请求,就不继续往下传递;如果不能处理,则交由后面的处理器来处理(也就是调用 successor.handle())。HandlerChain 是处理器链,从数据结构的角度来看,它就是一个记录了链头、链尾的链表。其中,记录链尾是为了方便添加处理器。

java 复制代码
public abstract class Handler {
    protected Handler successor = null;

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    public abstract void handle();
}

public class HandlerA extends Handler {
    @Override
    public void handle() {
        boolean handled = false;
        //...
        if (!handled && successor != null) {
            successor.handle();
        }
    }
}

public class HandlerB extends Handler {
    @Override
    public void handle() {
        boolean handled = false;
        //...
        if (!handled && successor != null) {
            successor.handle();
        } 
    }
}

public class HandlerChain {
    private Handler head = null;
    private Handler tail = null;

    public void addHandler(Handler handler) {
        handler.setSuccessor(null);

        if (head == null) {
            head = handler;
            tail = handler;
            return;
        }

        //这里表示单链表的next()指向操作,successor代表next(); 
        //上面if()进行head和tail的初始化工作,head和tail执行同一个节点; 
        //对于新插入的节点,先进行指定旧tail的next节点,再移动tail。 其实画一下图就方便理解了。
        tail.setSuccessor(handler);
        tail = handler;
    }

    public void handle() {
        if (head != null) {
            head.handle();
        }
    }
}

// 使用举例
public class Application {
    public static void main(String[] args) {
        HandlerChain chain = new HandlerChain();
        chain.addHandler(new HandlerA());
        chain.addHandler(new HandlerB());
        chain.handle();
    }
}

实际上,上面的代码实现不够优雅。处理器类的 handle() 函数,不仅包含自己的业务逻辑,还包含对下一个处理器的调用,也就是代码中的 successor.handle()。一个不熟悉这种代码结构的程序员,在添加新的处理器类的时候,很有可能忘记在 handle() 函数中调用 successor.handle(),这就会导致代码出现 bug。

对这个问题,我们对代码进行重构,利用模板模式,将调用 successor.handle() 的逻辑从具体的处理器类中剥离出来,放到抽象父类中。这样具体的处理器类只需要实现自己的业务逻辑就可以了。重构之后的代码如下所示:

java 复制代码
public abstract class Handler {
  protected Handler successor = null;

  public void setSuccessor(Handler successor) {
    this.successor = successor;
  }

  public final void handle() {
    boolean handled = doHandle();
    if (successor != null && !handled) {
      successor.handle();
    }
  }

  protected abstract boolean doHandle();
}

public class HandlerA extends Handler {
  @Override
  protected boolean doHandle() {
    boolean handled = false;
    //...
    return handled;
  }
}

public class HandlerB extends Handler {
  @Override
  protected boolean doHandle() {
    boolean handled = false;
    //...
    return handled;
  }
}

// HandlerChain和Application代码不变

我们再来看第二种实现方式,代码如下所示。这种实现方式更加简单。HandlerChain 类用数组而非链表来保存所有的处理器,并且需要在 HandlerChain 的 handle() 函数中,依次调用每个处理器的 handle() 函数。

java 复制代码
public interface IHandler {
  boolean handle();
}

public class HandlerA implements IHandler {
  @Override
  public boolean handle() {
    boolean handled = false;
    //...
    return handled;
  }
}

public class HandlerB implements IHandler {
  @Override
  public boolean handle() {
    boolean handled = false;
    //...
    return handled;
  }
}

public class HandlerChain {
  private List<IHandler> handlers = new ArrayList<>();

  public void addHandler(IHandler handler) {
    this.handlers.add(handler);
  }

  public void handle() {
    for (IHandler handler : handlers) {
      boolean handled = handler.handle();
      if (handled) {
        break;
      }
    }
  }
}

// 使用举例
public class Application {
  public static void main(String[] args) {
    HandlerChain chain = new HandlerChain();
    chain.addHandler(new HandlerA());
    chain.addHandler(new HandlerB());
    chain.handle();
  }
}

在 GoF 给出的定义中,如果处理器链上的某个处理器能够处理这个请求,那就不会继续往下传递请求。实际上,职责链模式还有一种变体,那就是请求会被所有的处理器都处理一遍,不存在中途终止的情况。这种变体也有两种实现方式:用链表存储处理器和用数组存储处理器,跟上面的两种实现方式类似,只需要稍微修改即可。

java 复制代码
public abstract class Handler {
  protected Handler successor = null;

  public void setSuccessor(Handler successor) {
    this.successor = successor;
  }

  public final void handle() {
    doHandle();
    if (successor != null) {
      successor.handle();
    }
  }

  protected abstract void doHandle();
}

public class HandlerA extends Handler {
  @Override
  protected void doHandle() {
    //...
  }
}

public class HandlerB extends Handler {
  @Override
  protected void doHandle() {
    //...
  }
}

public class HandlerChain {
  private Handler head = null;
  private Handler tail = null;

  public void addHandler(Handler handler) {
    handler.setSuccessor(null);

    if (head == null) {
      head = handler;
      tail = handler;
      return;
    }

    tail.setSuccessor(handler);
    tail = handler;
  }

  public void handle() {
    if (head != null) {
      head.handle();
    }
  }
}

// 使用举例
public class Application {
  public static void main(String[] args) {
    HandlerChain chain = new HandlerChain();
    chain.addHandler(new HandlerA());
    chain.addHandler(new HandlerB());
    chain.handle();
  }
}

应用场景举例:

对于支持 UGC(User Generated Content,用户生成内容)的应用(比如论坛)来说,用户生成的内容(比如,在论坛中发表的帖子)可能会包含一些敏感词(比如涉黄、广告、反动等词汇)。针对这个应用场景,我们就可以利用职责链模式来过滤这些敏感词。

对于包含敏感词的内容,我们有两种处理方式,一种是直接禁止发布,另一种是给敏感词打马赛克(比如,用 *** 替换敏感词)之后再发布。第一种处理方式符合 GoF 给出的职责链模式的定义,第二种处理方式是职责链模式的变体。

我们这里只给出第一种实现方式的代码示例,如下所示,并且,我们只给出了代码实现的骨架,具体的敏感词过滤算法并没有给出

java 复制代码
public interface SensitiveWordFilter {
  boolean doFilter(Content content);
}

public class SexyWordFilter implements SensitiveWordFilter {
  @Override
  public boolean doFilter(Content content) {
    boolean legal = true;
    //...
    return legal;
  }
}

// PoliticalWordFilter、AdsWordFilter类代码结构与SexyWordFilter类似

public class SensitiveWordFilterChain {
  private List<SensitiveWordFilter> filters = new ArrayList<>();

  public void addFilter(SensitiveWordFilter filter) {
    this.filters.add(filter);
  }

  // return true if content doesn't contain sensitive words.
  public boolean filter(Content content) {
    for (SensitiveWordFilter filter : filters) {
      if (!filter.doFilter(content)) {
        return false;
      }
    }
    return true;
  }
}

public class ApplicationDemo {
  public static void main(String[] args) {
    SensitiveWordFilterChain filterChain = new SensitiveWordFilterChain();
    filterChain.addFilter(new AdsWordFilter());
    filterChain.addFilter(new SexyWordFilter());
    filterChain.addFilter(new PoliticalWordFilter());

    boolean legal = filterChain.filter(new Content());
    if (!legal) {
      // 不发表
    } else {
      // 发表
    }
  }
}

特点:

应用设计模式主要是为了应对代码的复杂性,让其满足开闭原则,提高代码的扩展性:

职责链模式如何应对代码的复杂性:

将大块代码逻辑拆分成函数,将大类拆分成小类,是应对代码复杂性的常用方法。应用职责链模式,我们把各个敏感词过滤函数继续拆分出来,设计成独立的类,进一步简化了 SensitiveWordFilter 类,让 SensitiveWordFilter 类的代码不会过多,过复杂。

职责链模式如何让代码满足开闭原则,提高代码的扩展性:

当我们要扩展新的过滤算法的时候,比如,我们还需要过滤特殊符号,按照非职责链模式的代码实现方式,我们需要修改 SensitiveWordFilter 的代码,违反开闭原则。不过,这样的修改还算比较集中,也是可以接受的。而职责链模式的实现方式更加优雅,只需要新添加一个 Filter 类,并且通过 addFilter() 函数将它添加到 FilterChain 中即可,其他代码完全不需要修改。

即便使用职责链模式来实现,当添加新的过滤算法的时候,还是要修改客户端代码(ApplicationDemo),这样做也没有完全符合开闭原则。

我们可以把上面的代码分成两类:框架代码和客户端代码。其中,ApplicationDemo 属于客户端代码,也就是使用框架的代码。除 ApplicationDemo 之外的代码属于敏感词过滤框架代码。

假设敏感词过滤框架并不是我们开发维护的,而是我们引入的一个第三方框架,我们要扩展一个新的过滤算法,不可能直接去修改框架的源码。这个时候,利用职责链模式就能达到开篇所说的,在不修改框架源码的情况下,基于职责链模式提供的扩展点,来扩展新的功能。换句话说 ,我们在框架这个代码范围内实现了开闭原则。

除此之外,利用职责链模式相对于不用职责链的实现方式,还有一个好处,那就是配置过滤算法更加灵活,可以只选择使用某几个过滤算法。

总结:

在职责链模式中,多个处理器依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。

在 GoF 的定义中,一旦某个处理器能处理这个请求,就不会继续将请求传递给后续的处理器了。当然,在实际的开发中,也存在对这个模式的变体,那就是请求不会中途终止传递,而是会被所有的处理器都处理一遍。

职责链模式有两种常用的实现。一种是使用链表来存储处理器,另一种是使用数组来存储处理器,后面一种实现方式更加简单。

框架中常用的过滤器、拦截器是如何实现的

都是为了解耦代码,应对代码的复杂性,让代码满足开闭原则,提高代码的可扩展性。

职责链模式常用在框架的开发中,为框架提供扩展点,让框架的使用者在不修改框架源码的情况下,基于扩展点添加新的功能。实际上,更具体点来说,职责链模式最常用来开发框架的过滤器和拦截器。

Servlet Filter

它可以实现对 HTTP 请求的过滤功能,比如鉴权、限流、记录日志、验证参数等等。因为它是 Servlet 规范的一部分,所以,只要是支持 Servlet 的 Web 容器(比如,Tomcat、Jetty 等),都支持过滤器功能。为了帮助你理解,下面示意图阐述它的工作原理,如下所示。

在实际项目中,我们该如何使用 Servlet Filter 呢?我写了一个简单的示例代码,如下所示。添加一个过滤器,我们只需要定义一个实现 javax.servlet.Filter 接口的过滤器类,并且将它配置在 web.xml 配置文件中。Web 容器启动的时候,会读取 web.xml 中的配置,创建过滤器对象。当有请求到来的时候,会先经过过滤器,然后才由 Servlet 来处理。

java 复制代码
public class LogFilter implements Filter {
  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    // 在创建Filter时自动调用,
    // 其中filterConfig包含这个Filter的配置参数,比如name之类的(从配置文件中读取的)
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    System.out.println("拦截客户端发送来的请求.");
    chain.doFilter(request, response);
    System.out.println("拦截发送给客户端的响应.");
  }

  @Override
  public void destroy() {
    // 在销毁Filter时自动调用
  }
}

// 在web.xml配置文件中如下配置:
<filter>
  <filter-name>logFilter</filter-name>
  <filter-class>com.xzg.cd.LogFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>logFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

添加过滤器非常方便,不需要修改任何代码,定义一个实现 javax.servlet.Filter 的类,再改改配置就搞定了,完全符合开闭原则。那 Servlet Filter 是如何做到如此好的扩展性的呢?我想你应该已经猜到了,它利用的就是职责链模式。现在,我们通过剖析它的源码,详细地看看它底层是如何实现的。

我们讲到,职责链模式的实现包含处理器接口(IHandler)或抽象类(Handler),以及处理器链(HandlerChain)。对应到 Servlet Filter,javax.servlet.Filter 就是处理器接口,FilterChain 就是处理器链。接下来,我们重点来看 FilterChain 是如何实现的。

Servlet 只是一个规范,并不包含具体的实现,所以,Servlet 中的 FilterChain 只是一个接口定义。具体的实现类由遵从 Servlet 规范的 Web 容器来提供,比如,ApplicationFilterChain 类就是 Tomcat 提供的 FilterChain 的实现类,源码如下所示。

为了让代码更易读懂,我对代码进行了简化,只保留了跟设计思路相关的代码片段。完整的代码你可以自行去 Tomcat 中查看。

java 复制代码
public final class ApplicationFilterChain implements FilterChain {
  private int pos = 0; //当前执行到了哪个filter
  private int n; //filter的个数
  private ApplicationFilterConfig[] filters;
  private Servlet servlet;
  
  @Override
  public void doFilter(ServletRequest request, ServletResponse response) {
    if (pos < n) {
      ApplicationFilterConfig filterConfig = filters[pos++];
      Filter filter = filterConfig.getFilter();
      filter.doFilter(request, response, this);
    } else {
      // filter都处理完毕后,执行servlet
      servlet.service(request, response);
    }
  }
  
  public void addFilter(ApplicationFilterConfig filterConfig) {
    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;
  }
}

ApplicationFilterChain 中的 doFilter() 函数的代码实现比较有技巧,实际上是一个递归调用。你可以用每个 Filter(比如 LogFilter)的 doFilter() 的代码实现,直接替换 ApplicationFilterChain 的第 12 行代码,一眼就能看出是递归调用了。我替换了一下,如下所示。

vbscript 复制代码
  @Override
  public void doFilter(ServletRequest request, ServletResponse response) {
    if (pos < n) {
      ApplicationFilterConfig filterConfig = filters[pos++];
      Filter filter = filterConfig.getFilter();
      //filter.doFilter(request, response, this);
      //把filter.doFilter的代码实现展开替换到这里
      System.out.println("拦截客户端发送来的请求.");
      chain.doFilter(request, response); // chain就是this
      System.out.println("拦截发送给客户端的响应.")
    } else {
      // filter都处理完毕后,执行servlet
      servlet.service(request, response);
    }
  }

这样实现主要是为了在一个 doFilter() 方法中,支持双向拦截,既能拦截客户端发送来的请求,也能拦截发送给客户端的响应,你可以结合着 LogFilter 那个例子,以及对比待会要讲到的 Spring Interceptor,来自己理解一下。而我们上一节课给出的两种实现方式,都没法做到在业务逻辑执行的前后,同时添加处理代码。

Spring Interceptor

刚刚讲了 Servlet Filter,现在我们来讲一个功能上跟它非常类似的东西,Spring Interceptor,翻译成中文就是拦截器。尽管英文单词和中文翻译都不同,但这两者基本上可以看作一个概念,都用来实现对 HTTP 请求进行拦截处理。

Servlet Filter 是 Servlet 规范的一部分,实现依赖于 Web 容器。Spring Interceptor 是 Spring MVC 框架的一部分,由 Spring MVC 框架来提供实现。客户端发送的请求,会先经过 Servlet Filter,然后再经过 Spring Interceptor,最后到达具体的业务代码中。我画了一张图来阐述一个请求的处理流程,具体如下所示。

在项目中,我们该如何使用 Spring Interceptor 呢?我写了一个简单的示例代码,如下所示。LogInterceptor 实现的功能跟刚才的 LogFilter 完全相同,只是实现方式上稍有区别。LogFilter 对请求和响应的拦截是在 doFilter() 一个函数中实现的,而 LogInterceptor 对请求的拦截在 preHandle() 中实现,对响应的拦截在 postHandle() 中实现。

java 复制代码
public class LogInterceptor implements HandlerInterceptor {

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("拦截客户端发送来的请求.");
    return true; // 继续后续的处理
  }

  @Override
  public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    System.out.println("拦截发送给客户端的响应.");
  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    System.out.println("这里总是被执行.");
  }
}

//在Spring MVC配置文件中配置interceptors
<mvc:interceptors>
   <mvc:interceptor>
       <mvc:mapping path="/*"/>
       <bean class="com.xzg.cd.LogInterceptor" />
   </mvc:interceptor>
</mvc:interceptors>

同样,我们还是来剖析一下,Spring Interceptor 底层是如何实现的。

当然,它也是基于职责链模式实现的。其中,HandlerExecutionChain 类是职责链模式中的处理器链。它的实现相较于 Tomcat 中的 ApplicationFilterChain 来说,逻辑更加清晰,不需要使用递归来实现,主要是因为它将请求和响应的拦截工作,拆分到了两个函数中实现。HandlerExecutionChain 的源码如下所示,同样,我对代码也进行了一些简化,只保留了关键代码。

java 复制代码
public class HandlerExecutionChain {
 private final Object handler;
 private HandlerInterceptor[] interceptors;
 
 public void addInterceptor(HandlerInterceptor interceptor) {
  initInterceptorList().add(interceptor);
 }

 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
   for (int i = 0; i < interceptors.length; i++) {
    HandlerInterceptor interceptor = interceptors[i];
    if (!interceptor.preHandle(request, response, this.handler)) {
     triggerAfterCompletion(request, response, null);
     return false;
    }
   }
  }
  return true;
 }

 void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
   for (int i = interceptors.length - 1; i >= 0; i--) {
    HandlerInterceptor interceptor = interceptors[i];
    interceptor.postHandle(request, response, this.handler, mv);
   }
  }
 }

 void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
   throws Exception {
  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
   for (int i = this.interceptorIndex; i >= 0; i--) {
    HandlerInterceptor interceptor = interceptors[i];
    try {
     interceptor.afterCompletion(request, response, this.handler, ex);
    } catch (Throwable ex2) {
     logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
    }
   }
  }
 }
}

在 Spring 框架中,DispatcherServlet 的 doDispatch() 方法来分发请求,它在真正的业务逻辑执行前后,执行 HandlerExecutionChain 中的 applyPreHandle() 和 applyPostHandle() 函数,用来实现拦截的功能。具体的代码实现很简单,你自己应该能脑补出来,这里就不罗列了。感兴趣的话,你可以自行去查看。

Filter 可以拿到原始的http请求,但是拿不到你请求的控制器和请求控制器中的方法的信息;

Interceptor 可以拿到你请求的控制器和方法,却拿不到请求方法的参数;

Aop 可以拿到方法的参数,但是却拿不到http请求和响应的对象

三者应用范围不同: web filter 作用于容器,应用范围影响最大;spring interceptor 作用于框架,范围影响适中;aop 作用于业务逻辑,精细化处理,范围影响最小。

比如要对所有的web接口,进行统一的权限处理,不需要区分动作,写或者读,所有一视同仁,这种情况下,servlet的更加适合。

针对一些存在状态的,比如做一些统一的去参数转换,cookie转uid之类,以及通用检验uid是否符合当前权限,则很用mvc较好,

而aop粒度就可以分的更加细致了,在一些更新需要,查询不需要的,如分控,日志记录等,就比较适合

AOP、Servlet Filter、Spring Interceptor这三者可以从不同权限检查的范围大小的视角来应用:

  1. Servlet Filter

运维部门需要对只供内部访问的服务进行IP限制或访问审查时,在容器这一层增加一个Filter,在发布时发布系统自动加挂这个Filter,这样对上层应用就是透明的,内网IP地址段增减或审查规则调整都不需要上层应用的开发人员去关心。

  1. Spring Interceptor

由框架或基础服务部门来提供的微服务间相互调用的授权检查时,可以提供统一的SDK,由程序员在需要的服务上配置。

  1. AOP

业务应用内权限检查,可以把权限检查在统一模块中实现,通过配置由AOP加插拦截检查。

实战:

使用场景

职责链模式非常适用于对请求的处理场景上,尤其是下面几种场景:

  • 请求的处理需要顺序执行时:可以将处理者连接成一条链,这样所有的请求都会严格按照顺序通过每个处理者。
  • 请求的类型多样,但是对于类型和顺序都预先无法知道:预先按照顺序将处理者组装成链,将请求发送到链上,顺序询问每个处理者是否能够处理。
  • 需要在处理时动态改变处理者的顺序:可以在处理者中加入对其他处理者的引用,然后动态的插入和移除后续处理者,以改变处理顺序。
  • 流式设计:也就是每次处理都返回新的请求,这样可以继续使用其他处理者。客户端只需要按照需求组装处理顺序。

而在下面的场景中,仅仅使用职责链模式就不行了,可以考虑更换或者搭配其他模式进行设计:

  • 接受者需要动态的选择接受或者取消请求,或者多个接受者并不需要顺序执行,这种情况应当选择观察者模式
  • 业务场景简单并且后续没有拓展的需求,则不需要使用使用职责链模式增加复杂度。

缺陷

职责链模式虽然很好的解决了顺序处理的问题,但是在使用时我们要注意可能会存在以下的问题:

  • 部分请求可能无法达到链尾:因为链可以动态改变,某一个处理者的添加可能改变整个处理链,导致请求被提前处理。
  • 部分请求可能未被处理:如果不存在适合的处理者,请求会存在未处理的情况。
  • 链条设计不易过于复杂:动态链条如果设计过于复杂,可能存在回环或者跳跃的情况。

最佳实践

在职责链的应用中,最为常见的场景就是过滤和UI事件传递。我们来看一下对这两个场景的经典应用Spring Security和Chromium.

Spring Security过滤链

Spring Security的需求就是对请求进行认证和授权,拒绝未认证的请求,来保证服务的安全。其核心就是一组过滤链。也就是下图: 从上面来看,Spring Security定义了一系列Filter,这样就可以对请求进行一层层过滤,进入到指定的入口Servlet。这个和我们所说的职责链模式应用场景非常贴合(PS:Spring Security中还支持自定义Filter,我们将在之后的代理模式中讲解),那么我们来看一下Spring Security过滤链的流程:

java 复制代码
// 1. 入口:通过注解 @EnableWebSecurity 开启Spring Security,注意这里引入了WebSecurityConfiguration
...
@Import({ WebSecurityConfiguration.class,
        SpringWebMvcImportSelector.class,
        OAuth2ImportSelector.class })
...
public @interface EnableWebSecurity {
    ...;
}

然后我们看WebSecurityConfiguration,当@Import进行导入时,所有的@Bean注解后的方法也会调用

java 复制代码
...;
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
    // 2. 判断注册的configurer是否为空,这里的configurer就是各种Security配置(继承WebSecurityConfigurerAdapter)
    boolean hasConfigurers = webSecurityConfigurers != null
        && !webSecurityConfigurers.isEmpty();
    // 3. 如果没有任何confiurer注册过,这里会初始化一个
    if (!hasConfigurers) {
        WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
            .postProcess(new WebSecurityConfigurerAdapter() {
            });
        webSecurity.apply(adapter);
    }
    // 4. 这个是我们重点关注的地方
    return webSecurity.build();
}
...;

而Web Security构造职责链的过程就是在这个build过程中:

java 复制代码
// AbstractConfiguredSecurityBuilder.java
protected final O doBuild() throws Exception {
    synchronized (configurers) {
        ...;
        // 5. 之前主要是init和configure,我们主要看build的逻辑
        O result = performBuild();

        buildState = BuildState.BUILT;

        return result;
    }
}

// 6. 内部有三个继承类:WebSecurity, HttpSecurity, AuthenticationManagerBuilder
protected abstract O performBuild() throws Exception;

而Spring Security的默认Filter链主要是在HttpSecurity中实现:

java 复制代码
// 7. 这里可以看出为每一个路径创建一条职责链DefaultSecurityFilterChain
// 其中的filters是由HttpSecurity的方法中操作的configurer实现的
// 这里我们以csrf来举例
protected DefaultSecurityFilterChain performBuild() {
    filters.sort(comparator);
    return new DefaultSecurityFilterChain(requestMatcher, filters);
}

// 8. csrf filter的添加,这一步会添加到上面的configurer列表和filters列表
public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
    ApplicationContext context = getContext();
    return getOrApply(new CsrfConfigurer<>(context));
}

// 9. 而职责链的结构如下
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
    ...;
    private final RequestMatcher requestMatcher;
    private final List<Filter> filters;

    ...;

    public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
        this.requestMatcher = requestMatcher;
        this.filters = new ArrayList<>(filters);
    }
    ...;
}

而每一个Filter都有一个处理方法:

java 复制代码
// Filter.java
// 10. 这里可以看出Spring中的每个Filter都会接受请求,不主动停止传递
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException;

我们在分析Spring Security源码时,去除了其他无关的逻辑,其实Spring Security中的内部逻辑很复杂,也使用了很多其他的设计模式。我们这里主要关注如何来构造职责链的过程,和基本的职责链构造过程一致。

JS事件传递

在W3C标准中对事件传递的定义中,主要有两种:Capture, Bubbling:

  • Capture是从点击元素的最外层元素开始响应,一步步到点击元素。
  • Bubbling是从点击元素开始响应,到最外层元素

而事件传递的流程正如下图所示:

  1. 当事件发生时,会从根节点往下传递到目标元素,当这个过程中,如果有元素注册了Capture监听,则会运行这些监听事件。

  2. 到达目标节点后,会运行目标节点的监听事件。

  3. 如果目标节点注册了Bubbling类型,会进行事件冒泡,事件会从目标节点向上传递到根节点,调用每个节点的事件监听事件。

我们这里主要是来看Bubbling过程,因为DOM的树形结构,所以这个职责链的构造不需要额外空间,可以直接判断元素是否有父元素,如果有,就调用事件监听,然后继续传递。

java 复制代码
// https://github.com/chromium/chromium/blob/master/third_party/blink/renderer/core/dom/events/event_dispatcher.h

// 1. 从接口文件中,我们可到我们上面定义的传递过程
EventDispatchContinuation DispatchEventPreProcess(
      Node* activation_target,
      EventDispatchHandlingState*&);
EventDispatchContinuation DispatchEventAtCapturing();
EventDispatchContinuation DispatchEventAtTarget();
void DispatchEventAtBubbling();
void DispatchEventPostProcess(Node* activation_target,
                              EventDispatchHandlingState*);

我们重点看一下DispatchEventAtBubbling这个类:

java 复制代码
// https://github.com/chromium/chromium/blob/master/third_party/blink/renderer/core/dom/events/event_dispatcher.cc

inline void EventDispatcher::DispatchEventAtBubbling() {
  // 2. 首先获取链条长度
  wtf_size_t size = event_->GetEventPath().size();
  // 3. 遍历链条
  for (wtf_size_t i = 1; i < size; ++i) {
    const NodeEventContext& event_context = event_->GetEventPath()[i];
    if (event_context.CurrentTargetSameAsTarget()) {
      // 4. 如果是点击目标,则要区分Capture,Target和Bubbling过程的事件
      event_->SetEventPhase(Event::kAtTarget);
      event_->SetFireOnlyNonCaptureListenersAtTarget(true);
      event_context.HandleLocalEvents(*event_);
      event_->SetFireOnlyNonCaptureListenersAtTarget(false);
    } else if (event_->bubbles() && !event_->cancelBubble()) {
      // 5. 处理事件
      event_->SetEventPhase(Event::kBubblingPhase);
      event_context.HandleLocalEvents(*event_);
    } else {
      continue;
    }
    // 6. JS中没有通过返回值来判断是否继续传递,而是通过event.stopPropagation()来处理
    if (event_->PropagationStopped())
      return;
  }
  ...;
}

总结

对于职责链模式的关键,在我看来就两个字:传递。无论是请求、数据还是事件。当需要按照某种顺序进行传递处理的时候,那么我们就要思考是否可以使用职责链模式。

责任链设计模式实战

2.1、业务场景介绍

支付模块,获取支付链接流程:

  1. 确认是否需要发短信;
  2. 发短信;
  3. 验证短信;
  4. 支付校验;
  5. 获取支付链接。

抽取责任链:
(主要通过枚举状态来控制那个责任)

  1. 无行为责任;
  2. 支付链接获取成功责任;
  3. 确认是否需要发短信责任;
  4. 发短信;
  5. 获取支付链接责任。

2.2、代码实现

2.2.0、面向接口编程

java 复制代码
public interface IPaymentService {

    PaymentStatus doService(Payment payment);

    boolean support(Payment payment);

    String name();
}

2.2.1、无行为责任

java 复制代码
@Order(1)
@Slf4j
@Service
class A_Payment_NoAction implements IPaymentService {

    @Override
    public PaymentStatus doService(Payment payment) {
        //日志输出
        return payment.getStatus();
    }

    @Override
    public boolean support(Payment payment) {
        //根据状态等信息判断是否该类处理
        return true;
    }

    @Override
    public String name() {
        return "NoAction处理器,只返回当前状态";
    }

}

2.2.2、支付链接获取成功责任

java 复制代码
@Order(2)
@Slf4j
@Service
class A_Payment_ApplyDone implements IPaymentService {

    @Override
    public PaymentStatus doService(Payment payment) {
        //日志输出
        //处理逻辑
        return payment.getStatus();
    }

    @Override
    public boolean support(Payment payment) {
        //根据状态等信息判断是否该类处理
        return true;
    }

    @Override
    public String name() {
        return "流程结束支付";
    }

}

2.2.3、确认是否需要发短信责任

java 复制代码
@Order(3)
@Slf4j
@Service
class A_Payment_SmsRequirement implements IPaymentService {

    @Override
    public PaymentStatus doService(Payment payment) {
        //日志输出
        //处理逻辑
        return payment.getStatus();
    }

    @Override
    public boolean support(Payment payment) {
        //根据状态等信息判断是否该类处理
        return true;
    }

    @Override
    public String name() {
        return "确认是否需要发短信责任";
    }

}

2.2.4、发短信责任

java 复制代码
@Order(4)
@Slf4j
@Service
class A_Payment_SendSms implements IPaymentService {

    @Override
    public PaymentStatus doService(Payment payment) {
        //日志输出
        //处理逻辑
        return payment.getStatus();
    }

    @Override
    public boolean support(Payment payment) {
        //根据状态等信息判断是否该类处理
        return true;
    }

    @Override
    public String name() {
        return "发短信责任";
    }

}

2.2.5、获取支付链接责任

java 复制代码
@Order(5)
@Slf4j
@Service
class A_Payment_GetPaymentUrl implements IPaymentService {

    @Override
    public PaymentStatus doService(Payment payment) {
        //日志输出
        //处理逻辑
        return payment.getStatus();
    }

    @Override
    public boolean support(Payment payment) {
        //根据状态等信息判断是否该类处理
        return true;
    }

    @Override
    public String name() {
        return "获取支付链接责任";
    }

}

2.2.6、流程执行器

java 复制代码
@Slf4j
@Service
class PayProcessConductorImpl implements IPayProcessConductor {

    private List<IPaymentService> paymentServices

    @Override
    PrePayResponse startProcess(DtoPrePay prePay) {
        //流程第一步检查
        //适配责任
        IPrePayService service = baseRuleTypes.stream()
            .filter(api -> api.support(payment))
            .findFirst()
            .orElseThrow(() -> new CommonException(PARAMETER_ERROR.getCodeValue(), "暂不支持该类型"));
        //处理逻辑
        service.doService(payment)
        //状态转换
        //返回
        return response
    }


}

参考:

time.geekbang.org/column/arti...

time.geekbang.org/column/arti...

juejin.cn/post/703774...

zhuanlan.zhihu.com/p/141229270

blog.csdn.net/Java__EE/ar...

相关推荐
越甲八千4 小时前
重温设计模式--享元模式
设计模式·享元模式
码农爱java5 小时前
设计模式--抽象工厂模式【创建型模式】
java·设计模式·面试·抽象工厂模式·原理·23种设计模式·java 设计模式
越甲八千6 小时前
重温设计模式--中介者模式
windows·设计模式·中介者模式
犬余6 小时前
设计模式之桥接模式:抽象与实现之间的分离艺术
笔记·学习·设计模式·桥接模式
Theodore_10227 小时前
1 软件工程——概述
java·开发语言·算法·设计模式·java-ee·软件工程·个人开发
越甲八千8 小时前
重拾设计模式--组合模式
设计模式·组合模式
思忖小下11 小时前
梳理你的思路(从OOP到架构设计)_设计模式Composite模式
设计模式·组合模式·eit
机器视觉知识推荐、就业指导11 小时前
C++设计模式:组合模式(公司架构案例)
c++·后端·设计模式·组合模式
越甲八千12 小时前
重拾设计模式--工厂模式(简单、工厂、抽象)
c++·设计模式
重生之绝世牛码13 小时前
Java设计模式 —— 【结构型模式】外观模式详解
java·大数据·开发语言·设计模式·设计原则·外观模式