责任链模式——请求的分发传递和处理


Demo 地址: https://github.com/ooblee/HelloDesignPattern

1. 定义

责任链模式(Chain of Responsibility  Pattern):避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

一个线性流程的处理。

现实世界的模型:

  • 案件审判,地方法庭->县法院->市中级人民法院->省高级人民法院->最高人民法院。
  • 经费报销,经理 ->主管->总监->财务。

责任链模式-图示

上面的经费报销,报销申请沿着链往下走,如果有一个节点处理后就不再往下传递。

责任链模式会把每个节点封装成一个类,然后连接起来,传递请求直到被拦截处理掉。

2. 设计

责任链的核心在于抽象处理者的实现。

  • 抽象处理者,定义处理请求的接口,持有下一个处理类的引用。
  • 具体处理者,实现处理请求的接口,

具体处理者相互连接形成一个链表。请求沿着链表传递,直到被处理。

责任链模式-类图

我这里举例两种设计。

2.1. 链表实现

整体的实现类似于链表。

抽象处理者,会携带下一个处理者的引用。

public abstract class AbstractHandler {

    private AbstractHandler successor;

    public abstract void handleRequest();

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

    public AbstractHandler getSuccessor() {
        return successor;
    }

    public void next() {
        if (getSuccessor() != null) {
            getSuccessor().handleRequest();
        }
    }
}

具体处理者,如果需要传递请求,调用 next,否则不调用(效果等同于拦截请求)。

public class HandlerA extends AbstractHandler {

    public void handleRequest() {
        System.out.println("A 处理");

        // 传递给下一个处理节点
        next();
    }
}

使用者组装链并发送请求:

public class TestChainOfResponsibility {

    public static void main(String[] args) {

        AbstractHandler handlerA = new HandlerA();
        AbstractHandler handlerB = new HandlerB();
        AbstractHandler handlerC = new HandlerC();

        // 建链
        handlerA.setSuccessor(handlerB);
        handlerB.setSuccessor(handlerC);
        handlerC.setSuccessor(null);

        // 处理请求
        handlerA.handleRequest();
    }
}

2.2. 非链表实现

上面的设计有一个问题,就是作为责任链的节点,需要去持有下一个节点的引用。

是否可以把这层关系剥离出去,让节点更加独立?

这也是很多框架使用的设计,会增加一个 Chain 类来维持整个处理者列表,由 Chain 类来驱动任务的传递。

这里给出一个简单的模型。

抽象 Chain 类,定义驱动下一个处理者的方法:

public interface IHandlerChain {

    void handle(Request request, Response response);
}

抽象处理者,处理方法会携带 Chain 类:

public interface IHandler {

    void handleRequest(Request request, Response response, IHandlerChain chain);
}

具体 Chain 类,维持责任链表,实现驱动下一个处理者的方法:

public class HandlerChain implements IHandlerChain {

    private final List<IHandler> handlerList;
    private int currentPos;

    public HandlerChain(List<IHandler> handlerList) {
        this.handlerList = handlerList;
        currentPos = 0;
    }

    public void handle(Request request, Response response) {
        if (currentPos >= handlerList.size()) {
            return;
        }

        IHandler handler = handlerList.get(currentPos++);
        handler.handleRequest(request, response, this);
    }
}

具体处理者,实现处理方法,无需关联到其他处理者,非常独立。使用 Chain 来驱动请求的传递。

public class HandlerA implements IHandler {

    public void handleRequest(Request request, Response response, IHandlerChain chain) {
        System.out.println("A 处理了请求");

        // 传递下一个请求
        chain.handle(request, response);
    }
}

如果不传递的话不用调用 Chain 即可。

使用者组装链表,并发送请求获取响应:

public class TestChainOfResponsibility {

    public static void main(String[] args) {

        // 建链
        IHandler handlerA = new HandlerA();
        IHandler handlerB = new HandlerB();
        IHandler handlerC = new HandlerC();
        List<IHandler> handlerList = new ArrayList<IHandler>();
        handlerList.add(handlerA);
        handlerList.add(handlerB);
        handlerList.add(handlerC);
        IHandlerChain handlerChain = new HandlerChain(handlerList);

        // 处理请求
        Request request = new Request();
        Response response = new Response();
        handlerChain.handle(request, response);
    }
}

抽象出 Chain,让各个处理者更加独立,无任何关联。

抽象出 Chain 类还有一个好处,如果处理者需要关联到外部环境,需要用到环境上下文的一些对象,可以通过 Chain 类的传输。可以参考 OkHttp 的 RealInterceptorChain 的实现。

2.3. 纯责任链和不纯责任链

纯的责任链有这样的要求:

  • 处理节点必须承担全部责任或直接传递给下一个处理者。
  • 请求必须被一个处理节点处理掉。

按照纯责任链的设计,每个处理节点不会承担部分责任,然后请求也不会没有被接收的现象。

但实际应用大部分是不纯的责任链。

3. 应用

责任链可以在下面的场景使用。

一个请求有多个处理对象,然后:

  • 处理者可以动态配置
  • 处理者的顺序可以动态配置
  • 请求不需要指定具体的处理者

实际的应用根据业务场景会增加一些比较复杂的设计,提高扩展性和灵活性。

  • 比如会有封装好的请求和响应。响应通过处理方法返回,当前处理者可以获取到响应再进一步处理,比如 Servlet 的 Filter 链,OkHttp 的拦截器链。
  • 比如下一个处理者的关联和处理顺序单独放在一个类进行维护,各个处理节点不需要持有下一个处理节点的引用。比如 Servlet 的 FilterChain,Filter 可以调用 FilterChain 去触发下一个节点。

责任链各个处理节点独立变化,可以根据业务场景很方便地进行增删改。符合开闭原则

例如这样的场景:

要求这样处理 A -> B -> C。

需求变化,B 的重要性提升,需要提前处理,那么就可以改下连接关系 B -> A -> C。

需求变化,有多了一个 D 请求,需要在 A 和 C 之间执行,则 B -> A -> D -> C。

请求发起者不需要关心到这些变化或者过程,只要关心请求的发送和响应的接收。

3.1. Servlet 的 Filter 链

Java Servlet 用来获取请求,生成响应数据并返回。绝大部分用来处理 HTTP 协议。

Filter 属于 Servlet API 的一部分,可以用来对 HttpServletRequest 进行预处理。也可以对 HttpServletResponse 进行发送前处理。

在 Servelt 执行前,会先调用 Filter 对请求进行过滤。Servlet 返回的响应也可以通过 Filter 进行处理。

比如:

  • 对 HttpServletRequest 进行拦截
  • 检查或者修改 HttpServletRequest 的头部信息或者数据
  • 检查或者修改 HttpServletResponse 的头部信息或者数据

比如 MVC 框架 Structs2 就是采用 Filter 拦截 HttpServletRequest,根据 url 地址分发到对应的控制器进行处理。

请求 HttpServletRequest 在处理链中传递,各个处理节点完成自己的任务,比如:

  • 过滤非法请求
  • 加入日志
  • 编解码

Filter 类似于 AOP 编程中的一个切点,所有的 Filter 处理形成一个处理链。

整个实现使用了非链表实现的责任链模式。

抽象 Filter Chain 类:

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

抽象 Filter 类:

public interface Filter {

    public void init(FilterConfig filterConfig) throws ServletException;

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

    public void destroy();
}

具体 Filter 类,比如用来打个日志:

@WebFilter(filterName = "log", urlPatterns = {"/*"})
public class LogFilter implements Filter {

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

        System.out.println("处理请求前");

        chain.doFilter(request, response);

        System.out.println("处理请求后");
    }
}

组装过滤器链和发送请求由支持 Servlet 的 Web 服务器提供。

比如 Tomcat。在 ApplicationDispatcher 中的 invoke ,调用 ApplicationFilterFactory#createFilterChain 完成了对 FilterChain 的创建。

// Get the FilterChain Here
ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

然后调用,处理请求。

filterChain.doFilter(request, response);

3.2. OkHttp 的 Interceptor 链

相关文章:

OkHttp 是一款基于 Java 实现的 HTTP 客户端。在 Android 平台应用广泛(Android 官方已经移除 HttpClient 直接使用 OkHttp)。

OkHttp 的请求处理过程使用的是拦截器链实现的。

一个 HTTP 客户端,对于用户发起的 HTTP 请求,需要什么工作?

  • 连接失败的重试。
  • 重定向的处理。
  • 修改 header 信息。
  • 响应缓存的实现。
  • 建立连接。比如 TCP 连接或者 SSL 连接。
  • 往 TCP 连接中写入请求并读取响应

正好是责任链模式的应用场景:一个请求对应多个处理者。

OkHttp 设计了 Inteceptor,作为抽象处理者。处理方法为 intercept

设计了 Chain 用来维护责任链,驱动请求的传递,并且获取一些环境信息。驱动下一个处理者的方法为 proceed

public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {

	...

    Response proceed(Request request) throws IOException;
    ...
  }
}

Chain 的默认实现类为 RealInterceptorChain。处理者列表为 interceptors

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  ...
  private final int index;

...

  public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
      throws IOException {
    
    ...
    
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

   	...
    
    return response;
  }
}

具体的处理者,根据我们上面分析的处理任务有这些:

  • RetryAndFollowUpInterceptor,请求连接失败的重试或重定向。
  • BridgeInterceptor,用来修改请求和响应的 header 信息。
  • CacheInterceptor,比如获取到的 Response 带有 Date,Expires,Last-Modified,Etag 等 header,表示该 Response 可以缓存一定的时间,下次请求就可以不需要发往服务端,直接拿缓存的。
  • ConnectInterceptor,使用 Socket 创建 TCP 连接,如果是 HTTPS 还会进行 TLS 握手。
  • CallServerInterceptor,往 Socket 写入请求并读取响应。

OkHttp 组装和调用链的地方在 RealCall 里。看代码:

 Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

整个拦截器链的流程如下:

OkHttp 拦截器

3.3. Android 事件分发机制

相关文章:

任何 GUI 编程,都会遇到用户输入事件的处理。

在界面复杂,嵌套 View 非常复杂的情况下,有多个层级的情况下,会涉及到一个问题,事件该由哪个 View 来处理?

这里可以抽象成一个事件请求,存在多个处理者,应该由哪个处理者来执行的问题。

也是责任链模式的应用范畴了。

这里举 Android 事件分发机制的例子(其他平台其实原理也类似)。

在 Android 事件分发前,整个 View 树已经建立(这里使用的是组合模式)。处理者就是整棵 View 树的各个 View 节点。

事件会沿着整个 View 树传递,直到被拦截和消费。

事件核心为三个:

  • 按下,MotionEvent.ACTION_DOWN。
  • 移动,MotionEvent.ACTION_MOVE。
  • 起来,MotionEvent.ACTION_UP。

在用户在界面上手指按下的那一刻,会触发一个 ACTION_DOWN 事件,系统会取出当前的 Activity,调用 dispatchTouchEvent 方法开始整个事件分发流程。

而作为处理者的 View 调用 dispatchTouchEvent 来处理事件,决定是否拦截和消费事件,或者继续传递给子 View。

整个流程如下:

Android 事件分发机制

4. 特点

4.1. 优势

  • 解耦:请求发起者和请求响应者进行解耦。链的创建由使用者(客户端)创建。
  • 动态组合:可以运行时动态组合链,根据需求增加或者减少处理节点。
  • 易修改:处理节点独立变化,只处理自己关心的工作,修改方便。
  • 易扩展:如果有新的处理需求,创建新的处理节点,然后使用者重新建链。是符合开闭原则的。

4.2. 缺点

  • 执行未处理:没有明确接收者,或者配置不当,会出现请求没有处理。
  • 执行的路径长:一次任务的处理需要多个节点的传递,性能会降低,调试不方便。
  • 执行的死循环:建链不当导致环型链表,死循环。
发布了61 篇原创文章 · 获赞 43 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/firefile/article/details/90314292