责任链

Chain of Responsibility

将多个不同职责的处理者连接为一条链,一个请求沿着这条链进行发送,每个处理者都可以对请求进行处理,处理完成后丢弃或者传递给链中的下一个处理者;

在 OkHttp的拦截器中,就是采用了责任链模式,而且请求沿着责任链处理后,发送得到响应后,还会沿着 责任链的反方向 继续处理;

什么时候使用呢?

  • 当需要使用不同的方式处理不同种类的请求时;

  • 当需要按某种顺序(这个顺序可以是动态的)执行多个处理者时;

责任链的实现基本可以分为两种:

  • 一种是单向传递:可以使用单链表或者数组来实现;

  • 另一个是双向传递(从左到右,从右到左):可以使用 递归 去实现

无论哪种实现,实际上都是对一系列不同处理的分层处理;

单向传递的责任链

责任链中的所有处理对象都应该实现同一个接口:

 // 这里使用 Kotlin 的函数式接口,方便编写不同的处理者
 fun interface Processor {
     fun process(req: Request): Boolean
 }

这个 process 函数的返回值根据实际需求定义:

  • 如果是检查某些条件是否合适,那么可以返回 Boolean 来表示检查是否通过;

  • 如果是会根据传入的参数构建新的实例,那么可以返回对应的实例类型,以表示是否需要覆盖原本的值

     // 这个 Request 可以是创建新的实例,也可以是没有创建前的实例
     fun process(req: Request): Request

使用数组

使用数组来对所有责任链上的处理者调用;

 class Request {
     var name: String? = null
 ​
     var header: String? = null
 ​
     override fun toString(): String {
         return "Request(name: $name, header: $header)"
     }
 }
 ​
 ​
 fun main() {
 ​
     val processors = mutableListOf(
         Processor {
             // 起个名字
             it.name = "processor1"
             println("processor1 handle")
             true
         },
 ​
         Processor {
             // 加个请求头
             it.header = "processor2"
             println("processor2 handle")
             true
         },
     )
     val req = Request()
     for (processor in processors) {
         val state = processor.process(req)
         if(!state) {
             break // or throw Exception
         }
     }
     println(req)
 }
 ​
 /*
 运行结果:
 processor1 handle
 processor2 handle
 Request(name: processor1, header: processor2)
 */

使用单链表

通过给拦截器加上指向下一个拦截器的字段来实现

同样也是相同的迭代,只是改成了单链表的迭代方式;

此处不再赘述


双向传递的责任链

从左到右传递请求,得到结果后,从右到左传递结果;

通常会使用 递归 去实现

声明一个 Request 和 Response

 class Request {
     var name: String? = null
     var header: String? = null
     
     override fun toString(): String {
         return "Request(name: $name, header: $header)"
     }
 }
 ​
 ​
 class Response {
     var name: String? = null
     var req: Request? = null
     var header: String? = null
 ​
     override fun toString(): String {
         return "Request(name: $name, header: $header, req: $req)"
     }
 }

如果按照上面单向迭代的方式,可能会想到这种责任链处理者的接口;

 // 错误的
 fun interface Processor {
     fun process(req: Request): Response
 }

有多个拦截器,但是每个拦截器都需要 Request 参数,而第一个拦截器返回了 Response,那下一个拿什么传进入?

此时这种类似数组的迭代方式就无法满足现在的需求了;

也就是说,需要用另外一些东西去充当处理者之间的 “桥梁”:

  • 维护当前传递到责任链的哪个部分?

  • 将 Request 传递给责任链下一个处理者

  • 将 Response 返回给责任链的前一个处理者

 class Chain(
     val processors: List<Processor>, // 责任链链
     val processIndex: Int,
     val request: Request
 ) {
     // 核心方法
     fun deliver(req: Request): Response {
         // 当前处理的拦截器
         val processor = processors[processIndex]
         // 下一个桥梁:也是指定了下一个交给责任链的位置
         val nextChain = Chain(processors, processIndex + 1, req)
         // 当前处理者返回的结果
         val response = processor.process(nextChain)
         return response
     }
 }

接下来的处理者实现:

 fun main() {
 ​
     val processors = mutableListOf(
         Processor { chain ->
             chain.request.name = "processor1"
             println("processor1 handle request ${chain.request}")
             // 拿到的是 Processor2 的结果
             val response = chain.deliver(chain.request)
             println("processor1 handle response $response")
             response.apply {
                 name = "processor1"
             }
         },
 ​
         Processor { chain ->
             chain.request.header = "processor2"
             println("processor2 handle request ${chain.request}")
             // 拿到的是 Processor3 的结果
             val response = chain.deliver(chain.request)
             println("processor2 handle response $response")
             response.apply {
                 header = "processor2"
             }
         },
 ​
         Processor { chain ->
             println("process3 handle and return response")
             Response().apply { req = chain.request }
         }
     )
     val request = Request()
     val chain = Chain(processors, 0, request)
     val response = chain.deliver(request)
     println("completed handle $response")
 }
 ​
 /*
 运行结果
 processor1 handle request Request(name: processor1, header: null)
 processor2 handle request Request(name: processor1, header: processor2)
 process3 handle and return response
 processor2 handle response Request(name: null, header: null, req: Request(name: processor1, header: processor2))
 processor1 handle response Request(name: null, header: processor2, req: Request(name: processor1, header: processor2))
 completed handle Request(name: processor1, header: processor2, req: Request(name: processor1, header: processor2))
 */

从上面的例子中,可以看到这就是一个递归的过程,以 Processor3 作为递归的终点(不继续向下传递而是直接返回)


OkHttp责任链实现(源码)

OkHttp的拦截器接口 Interceptor 中同时还包含了 Chain 接口的声明:

  • Interceptor 是一个函数式接口:

     fun interface Interceptor {
       @Throws(IOException::class)
       fun intercept(chain: Chain): Response
      
         // omit..
     }

    intercept 就是拦截器的核心处理方法

  • 内部声明了 Chain 接口:

    interface Chain {
        @Throws(IOException::class)
        fun proceed(request: Request): Response
        // 省略其他方法
    }

    process 方法就是沿着责任链向下传递的关键

getResponseWithInterceptorChain

RealCall 也就是封装的网络请求类中, getResponseWithInterceptorChain 方法是将请求发送出去,经过拦截链后拿到响应结果;

拦截链的设置也是在此处完成的,从左到右分别是

  • OkHttpClient设置的自定义拦截器

  • 重定向拦截器 RetryAndFollowUpInterceptor

  • 桥拦截器 RetryAndFollowUpInterceptor:负责给请求添加所需的请求头,以及根据需要给拿到的响应进行解压;

  • 缓存拦截器 CacheInterceptor:负责HTTP缓存的处理;

  • 连接拦截器 ConnectInterceptor:负责建立与目标服务器的连接;

  • 发送请求拦截器:CallServerInterceptor最后一个拦截器,将请求发送出去;

// RealCall#getResponseWithInterceptorChain

val interceptors = mutableListOf<Interceptor>()
// OkHttpClient 构建时的应用
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
  interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)

什么时候将请求传入?

// RealCall#getResponseWithInterceptorChain

val chain = RealInterceptorChain(
    call = this, 
    interceptors = interceptors,
    index = 0,
    exchange = null,
    request = originalRequest, // 需要发送的请求
    connectTimeoutMillis = client.connectTimeoutMillis,
    readTimeoutMillis = client.readTimeoutMillis,
    writeTimeoutMillis = client.writeTimeoutMillis
)

...

// 传入拦截器链,开始处理这个请求;
val response = chain.proceed(originalRequest)

究竟是怎么实现 Request 经过拦截器链处理后发送得到响应还能经过拦截器回来的呢?

  • 多个拦截器的一去一回,不难联想到递归;

  • 所有的拦截器,接收 Chain,相当于拿到 Request,通过 Chain.proceed 拿到对应的 Response

  • 那递归总得有一个起点和终点吧:上面的 chain.proceed 就是启动,而终点自然就是拦截链的最后一个拦截器 CallServerInterceptor

拦截链起点

RealInterceptorChain 是拦截链的起点,也是沿着拦截链传递的“驱动”;

内部保存了一整个拦截链 interceptors,当前请求在拦截器的位置 index;

从上面实例化看到其 index 的初始值为 0,也就是说从第一个拦截器开始调用;

override fun proceed(request: Request): Response {
  check(index < interceptors.size)
  ...
  // Call the next interceptor in the chain.
  val next = copy(index = index + 1, request = request)
  val interceptor = interceptors[index]
    
  val response = interceptor.intercept(next) ?: throw NullPointerException(
      "interceptor $interceptor returned null")
  
  ...
  return response
}

递归调用,每次调用 intercept 时都会指定下一个拦截器的位置 index + 1

在拦截器的 intercept 方法中会调用 chain.proceed(request) 即回到这处代码重复执行;

graph TB A(RealInterceptorChain) --interceptors-0#intercept--> B(interceptors-0) B --chain.proceed --> C(RealInterceptorChain-copy-1) C --interceptors-0#intercept--> D(interceptor-1) D --重复--> E(CallServerInterceptor)

拦截链终点

处于拦截链的最后一个拦截器 CallServerInterceptor,负责将请求发送出去,并接收其响应;

在其 intercept 方法,并没有调用 Chain.proceed 继续往下传递(因为已经是最后一个了);

此时获取响应并进行一次处理后,就会返回 Response 对象;

(此时就是递归过程中的 “归”)

graph TB A("CallServerInterceptor") --"return response"--> B(RealInterceptorChain-copy-n) B--"return response"-->C("中间的拦截器 response=chain.proceed(chain)") C--"return response"--> D("RealInterceptorChain")

总结

为什么要用一个 RealInterceptorChain, 而不是直接遍历两次 interceptors ?

  • 在 RealInterceptorChain 内部有对某些情况的处理,单独封装;

  • Interceptor 的拦截逻辑可能涉及到 Request 和 Response 的信息,使用 RealInterceptorChain 来传递请求和响应能保证对 Request 和 Response 的访问是同一次调用

  • Intercetpor 能够提前返回 Response ,比如 CacheInterceptor提前返回缓存的响应数据;如果使用两次遍历无法处理这种情况,这些情况适合 递归 去实现;

RealInterceptorChain 承载了整个拦截链,并驱动其运行;