OkHttp3 拦截器源码分析

OkHttp 内置拦截器

在这篇博客 OkHttp3 拦截器(Interceptor) ,我们已经介绍了拦截器的作用,拦截器是 OkHttp 提供的对 Http 请求和响应进行统一处理的强大机制,它可以实现网络监听、请求以及响应重写、请求失败充实等功能。

同时也了解了拦截器可以被链接起来使用,我们可以注册自定义的拦截器(应用拦截器和网络拦截器)到拦截器链上,如下图:

实际上除了我们自定义的拦截器外,OkHttp 系统内部还提供了几种其他的拦截器,就是上图中 OkHttp core 的部分。OkHttp 内部的拦截器各自负责不同的功能,每一个功能就是一个 Interceptor,这些拦截器连接起来形成了一个拦截器链,最终也就完成了一次网络请求。

具体如下图:

在上一篇博客 OkHttp3 源码分析 中,我们分析了 OkHttp 的同步和异步请求的流程源码,发现无论是同步请求还是异步请求都是通过调用 RealCall 的 getResponseWithInterceptorChain() 方法来获取 response 响应的。

RealCall. getResponseWithInterceptorChain()源码:

Response getResponseWithInterceptorChain() throws IOException {
    List<Interceptor> interceptors = new ArrayList();
    //添加自定义的应用拦截器
    interceptors.addAll(this.client.interceptors());
    //负责重定向和失败重试的拦截器
    interceptors.add(this.retryAndFollowUpInterceptor);
    //桥接网络层和应用层,就是为用户所创建的请求补充添加一些服务端还必需的 http 请求头等
    interceptors.add(new BridgeInterceptor(this.client.cookieJar()));
    //负责读取缓存,更新缓存
    interceptors.add(new CacheInterceptor(this.client.internalCache()));
    //负责与服务端建立连接
    interceptors.add(new ConnectInterceptor(this.client));
    //配置自定义的网络拦截器
    if (!this.forWebSocket) {
        interceptors.addAll(this.client.networkInterceptors());
    }
    //向服务端发送请求,从服务端读取响应数据
    interceptors.add(new CallServerInterceptor(this.forWebSocket));
    //创建 拦截器链chain 对象,这里将各种拦截器的 List 集合传了进去
    Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis());
    //通过链式请求得到 response
    return chain.proceed(this.originalRequest);
}

在这个方法中,我们就发现了 OkHttp 内置的这几种拦截器,这几种拦截器的具体作用稍后再说,先来宏观的分析一下 getResponseWithInterceptorChain() 做了些什么工作:

  1. 创建了一系列的拦截器,并将其放入一个拦截器 List 集合中。
  2. 将拦截器的 List 集合传入 RealInterceptorChain 的构造方法中,创建出一个拦截器链 RealInterceptorChain 。
  3. 执行拦截器链 chain 的 proceed() 方法来依次调用每个不同功能的拦截器,最终获取响应。

那么这个 Chain 对象到底是如何处理拦截器集合的呢,为什么通过调用 chain.proceed 就能得到被拦截器链依次处理之后的 response 呢?

其实这个问题的答案就是责任链设计模式,建议先了解一下关于责任链模式的介绍,再回头往下看。

在理解了责任链模式之后,我们就能比较容易的理解拦截器是如何工作的了。

首先来看一看 Interceptor 接口,很明显的它就是责任链模式中的抽象处理者角色了,各种拦截器都需要实现它的 intercept 方法

/**
 * Observes, modifies, and potentially short-circuits requests going out and the corresponding
 * responses coming back in. Typically interceptors add, remove, or transform headers on the request
 * or response.
 */
public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    ...
  }
}

这里我们注意到 Interceptor 还包含了一个内部接口 Chain,通过查看 Chain 接口,也可以大概了解它的功能:

  1. 通过 request() 方法来获取 request 请求
  2. 通过 proceed(request) 方法来处理 request 请求,并返回 response 响应

刚刚也介绍了在 getResponseWithInterceptorChain() 方法中,正是由 Chain 来依次调用拦截器来获取 response 的:

    //创建 拦截器链chain 对象,这里将各种拦截器的 List 集合传了进去
    Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis());
    //通过链式请求得到 response
    return chain.proceed(this.originalRequest);

那么它具体是怎么工作的呢?我们先来看一下RealInterceptorChain 的构造方法

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final RealConnection connection;
  private final int index;
  private final Request request;
  private final Call call;
  private final EventListener eventListener;
  private final int connectTimeout;
  private final int readTimeout;
  private final int writeTimeout;
  private int calls;

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
      EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
    this.call = call;
    this.eventListener = eventListener;
    this.connectTimeout = connectTimeout;
    this.readTimeout = readTimeout;
    this.writeTimeout = writeTimeout;
  }

需要特别注意的是这个构造方法里的 index 参数,传入给构造方法的 index 最终被赋值给了一个全局变量 index(这个变量很重要,之后会被使用到)。在构造出了 RealInterceptorChain 对象之后,接着就调用它的 proceed 方法来执行拦截器了。

来看一下 chain.proceed(request) 方法的具体实现:

RealInterceptorChain#proceed:

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    ...

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    //从拦截器集合中获取当前拦截器
    Interceptor interceptor = interceptors.get(index);
    //调用当前的拦截器的 intercept 方法获取 response
    Response response = interceptor.intercept(next);

    ...

    return response;
  }

这个方法的关键逻辑在这与这三行代码

一, 在 chain.proceed 的方法中,又 new 了一个 RealInterceptorChain,不过这里传入的参数是 index + 1,也就是说,每次调用 proceed 方法,都会产生出一个 index成员变量 +1的 RealInterceptorChain 对象。而且该 chain 对象的名字为 next,所以我们大致也能猜测一下它代表的是下一个 chain 对象。

    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);

二, 根据 index 索引值获取当前拦截器,这个 index 就是之前创建 chain 构造函数时的 index 值

大家应该还记得在 getResponseWithInterceptorChain 第一次创建 Chain 对象时,index被初始化为0。

Interceptor interceptor = interceptors.get(index);

三, 调用当前拦截器的 intercept(Chain chain) 方法

Response response = interceptor.intercept(next);

这里我们就以 index 为 0 为例,获取 interceptors 集合中的第一个拦截器 RetryAndFollowUpInterceptor(假设没有添加用户自定义的应用拦截器),来看一下它的 intercept 方法:

  @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();

    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    this.streamAllocation = streamAllocation;

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
          throw e.getFirstConnectException();
        }
        releaseConnection = false;
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
        // We‘re throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

    ...
    }
  }

这段代码中最关键的地方是:

response = realChain.proceed(request, streamAllocation, null, null);

我们发现,原来在 intercept() 中又会调用 chain.proceed() 方法,而每次调用 proceed 方法中又会去获取一个索引为 index + 1 的下一个拦截器,并执行该拦截器的 intercept() 方法,就是这样相互的递归调用,实现了对拦截器的逐步调用。

这个过程流程图如下:

到这里也许我们会有一个疑问,那就是为什么每次都需要创建一个新的 RealInterceptorChain 对象,只需要修改 index 变量的值不是也能实现同样的效果吗?这里的原因是 RealInterceptorChain 对象中还包含了 request 请求信息在内的其他信息,而每次执行拦截器的 intercept 方法时,因为递归调用的缘故,本层 的 intercept 并没有被执行完,如果复用 RealInterceptorChain 对象,则其他层次会对本层次 RealInterceptorChain 对象产生影响。

参考

https://blog.csdn.net/aiynmimi/article/details/79643123

https://blog.csdn.net/qq_16445551/article/details/79008433

原文地址:https://www.cnblogs.com/liyutian/p/9536852.html

时间: 2024-08-30 05:09:50

OkHttp3 拦截器源码分析的相关文章

[原创]java WEB学习笔记70:Struts2 学习之路-- struts2拦截器源码分析,运行流程

本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱好者,互联网技术发烧友 微博:伊直都在0221 QQ:951226918 -----------------------------------------------------------------------------------------------------------------

linux调度器源码分析 - 运行(四)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 引言 之前的文章已经将调度器的数据结构.初始化.加入进程都进行了分析,这篇文章将主要说明调度器是如何在程序稳定运行的情况下进行进程调度的. 系统定时器 因为我们主要讲解的是调度器,而会涉及到一些系统定时器的知识,这里我们简单讲解一下内核中定时器是如何组织,又是如何通过通过定时器实现了调度器的间隔调度.首先我们先看一下内核定时器的框架 在内核中,会使用strut clock_event_device结构描述硬件

linux调度器源码分析 - 初始化(二)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 引言 上期文章linux调度器源码分析 - 概述(一)已经把调度器相关的数据结构介绍了一遍,本篇着重通过代码说明调度器在系统启动初始化阶段是如何初始化和工作的.通过上期文章我们知道,在多核CPU和SMP系统中,每个CPU(多核COU中的每个核)都有自己的struct rq队列,而rq队列中又有着自己的struct cfs_rq和struct rt_rq.在初始化时就是对这三个结构进行初始化. init_tas

0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

1.概述 Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理.例如通过拦截器可以进行权限验证.记录请求信息的日志.判断用户是否登录等. 2.简单示例 2.1.继承 HandlerInterceptorAdapter 抽象类实现一个拦截器.代码如下: public class DemoInterceptor extends HandlerInterceptorAdapter { @Override    pu

Struts2 源码分析-----拦截器源码解析 --- ParametersInterceptor

ParametersInterceptor拦截器其主要功能是把ActionContext中的请求参数设置到ValueStack中,如果栈顶是当前Action则把请求参数设置到了Action中,如果栈顶是一个model(Action实现了ModelDriven接口)则把参数设置到了model中. 下面是该拦截器的doIntercept方法源码: @Override public String doIntercept(ActionInvocation invocation) throws Excep

关于阅读Struts2部分拦截器源码的记录

Struts2中的拦截器在ActionInvocation对象的invoke()方法中执行. ActionInvocation对象从配置文件中读取Interceptor对象,加入到自己的存取拦截器的容器中. 在invoke()方法中,当容器中还有Interceptor对象时,就执行对应Interceptor的intercept方法;在intercept方法中,除了加入拦截器自己的部分语句,还会调用同一个ActionInvocation对象的invoke方法,然后invoke方法再执行下一个Int

springMVC 拦截器源码解析

直接上源码 //doDispatch方法 //1.处理器拦截器的预处理(正序执行) HandlerInterceptor[] interceptors = mappedHandler.getInterceptors(); if (interceptors != null) { for (int i = 0; i < interceptors.length; i++) { HandlerInterceptor interceptor = interceptors[i]; if (!intercep

Mybatis拦截器源码深度解析

目录: 一. 建立拦截器链1. 创建对象2. 建立配置文件3. 加载拦截器链二. 方法调用解析1. 对请求对象进行拦截器包装2. 执行调用三. 小结 Mybatis拦截器 可以帮助我们在执行sql语句过程中增加插件以实现一些通用的逻辑,比如对查询sql分页.数据权限处理等. 允许使用插件拦截的方法调用包括: - Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

文件管理器源码分析(一)

Aboutmillet file management source code analysis Openthe file manager millet, we will soon see the interface as shownbelow: Amongthem, will be a variety of document classification. And show thenumber of each file. Androidframework of the MediaStore h