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

2023年 8月 13日 101.7k 0

概述

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

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

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

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

设计模式解决的问题:

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

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

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

行为型

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

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

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

职责链模式

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

定义:

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

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

代码实现:

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

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

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() 的逻辑从具体的处理器类中剥离出来,放到抽象父类中。这样具体的处理器类只需要实现自己的业务逻辑就可以了。重构之后的代码如下所示:

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() 函数。

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

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 给出的职责链模式的定义,第二种处理方式是职责链模式的变体。

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

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 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 来处理。

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配置文件中如下配置:

  logFilter
  com.xzg.cd.LogFilter


    logFilter
    /*

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

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

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

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

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 行代码,一眼就能看出是递归调用了。我替换了一下,如下所示。


  @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() 中实现。

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

   
       
       
   

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

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

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 = 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这三者可以从不同权限检查的范围大小的视角来应用:

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

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

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

    实战:

    使用场景

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

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

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

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

    缺陷

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

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

    最佳实践

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

    Spring Security过滤链

    Spring Security的需求就是对请求进行认证和授权,拒绝未认证的请求,来保证服务的安全。其核心就是一组过滤链。也就是下图:

    从上面来看,Spring Security定义了一系列Filter,这样就可以对请求进行一层层过滤,进入到指定的入口Servlet。这个和我们所说的职责链模式应用场景非常贴合(PS:Spring Security中还支持自定义Filter,我们将在之后的代理模式中讲解),那么我们来看一下Spring Security过滤链的流程:

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

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

    ...;
    @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过程中:

    // 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中实现:

    // 7. 这里可以看出为每一个路径创建一条职责链DefaultSecurityFilterChain
    // 其中的filters是由HttpSecurity的方法中操作的configurer实现的
    // 这里我们以csrf来举例
    protected DefaultSecurityFilterChain performBuild() {
        filters.sort(comparator);
        return new DefaultSecurityFilterChain(requestMatcher, filters);
    }
    
    // 8. csrf filter的添加,这一步会添加到上面的configurer列表和filters列表
    public CsrfConfigurer 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 filters;
    
        ...;
    
        public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List filters) {
            this.requestMatcher = requestMatcher;
            this.filters = new ArrayList(filters);
        }
        ...;
    }
    

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

    // 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是从点击元素开始响应,到最外层元素

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

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

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

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

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

    // 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这个类:

    // 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 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、业务场景介绍

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

  • 确认是否需要发短信;
  • 发短信;
  • 验证短信;
  • 支付校验;
  • 获取支付链接。
  • 抽取责任链:
    (主要通过枚举状态来控制那个责任)

  • 无行为责任;
  • 支付链接获取成功责任;
  • 确认是否需要发短信责任;
  • 发短信;
  • 获取支付链接责任。
  • 2.2、代码实现

    2.2.0、面向接口编程

    public interface IPaymentService {
    
        PaymentStatus doService(Payment payment);
    
        boolean support(Payment payment);
    
        String name();
    }
    

    2.2.1、无行为责任

    @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、支付链接获取成功责任

    @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、确认是否需要发短信责任

    @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、发短信责任

    @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、获取支付链接责任

    @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、流程执行器

    @Slf4j
    @Service
    class PayProcessConductorImpl implements IPayProcessConductor {
    
        private List 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…

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论