OkHttp源码之责任链模式

作者: 低情商的大仙 | 来源:发表于2018-10-05 11:17 被阅读35次

    对于okhttp来说,它的拦截器各位肯定是非常熟悉的,正是由于它的存在,okhttp的扩展性极强,对于调用方来说可谓是非常友好。而实现这个拦截器的就是大名鼎鼎的责任链模式了,其实整个okhttp的核心功能都是基于这个拦截器的,由各种不同的拦截器实现的。所以今天我们分析下okhttp的责任链模式。

    基本源码

    对于okhttp的Call来说,发起请求最终其实都调用了一个方法获取到Response的,同步异步都是一样的:

    Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        List<Interceptor> interceptors = new ArrayList<>();
        interceptors.addAll(client.interceptors());
        interceptors.add(retryAndFollowUpInterceptor);
        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, null, null, null, 0,
            originalRequest, this, eventListener, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());
    
        return chain.proceed(originalRequest);
      }
    

    前面只是加入一些必要的拦截器,是一些具体功能的实现,我们今天并不是分析这些具体功能,而是分析这种特殊的架构,所以这些拦截器就略过,我们重点看这里

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

    核心就是构造了一个chain,这个chain我们重点关注的是interceptors和index(此时是第一个节点,传入的是0),然后调用chain.proceed()方法获取最终结果。
    那么我们继续跟进去:

    public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
          RealConnection connection) throws IOException {
         //此处省略其他代码
    
        // 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);
        Response response = interceptor.intercept(next);
         //此处省略其他代码
        return response;
      })
    

    可以看到,每一个chain都是链条上的一个节点,chain的proceed()方法首先是获取下一个节点,然后获取当前节点对应的拦截器,将下一个节点当做参数给了拦截器的intercept()方法,在interceptor中会手动调用下一个chain的chain.proceed方法,将这条链走下去。这里有一点要注意,此处是先获取的下一个chain,也就是说我们在第n个拦截器中会调用第n+1个chain的proceed()方法,这样才能做到请求之前添加自定义行为(调用第n+1个chain的proceed()之前)和请求之后(调用第n+1个chain的proceed()之后)添加自定义行为。

    现有结构疑问

    现在,大家应该都知道这条链是怎么传下去的,但大家可能会感到很好奇,为什么这里会有两个角色,interceptor和chain,而且这两个结构似乎不是传统意义上的client和handler的角色(经典的责任链模式主要是这两个角色),因为这里interceptor中会调用chain的方法,这一行为似乎有点反常(此处的client角色应该是RealCall)。由于暴露给外界的接口其实是interceptor,这里我们尝试抛弃chain看会怎样,下面是一个链表结构的经典责任链实现:

    public class InterceptorChain {
        Response proceed(Request request){
            //此处意味着拿到第一个节点,具体怎么拿到不用管,仅仅做示意
            Interceptor first = getFirstInterceptor();
            return first.intercept(request);
        }
    
        public abstract class  Interceptor{
            protected Interceptor next;
            abstract Response intercept(Request request);
            void setNext(Interceptor interceptor){
                next = interceptor;
            }
        }
    }
    

    经典的责任链两个角色,client(此处的InterceptorChain)和handler(此处的Interceptor)
    此处我们尝试去实现okhttp的日志拦截器,主要实现:

    • 请求参数获取
    • 请求结果获取
    public class LogInterceptor extends Interceptor{
    
            @Override
            Response intercept(Request request) {
                //此处getRequestParams是伪代码,仅做示意
                Map<String,String> requestParams = request.getRequestParams();
                Response response= next.intercept(request);
                //此处getResult是伪代码,仅做示意
                String result = response.getResult();
                return response;
            }
        }
    

    可以看到,没有chain这个类,我们只是借助interceptor也是能实现现有的结构的。那么这两种结构有本质区别吗?我们再看下正常的okhttp的日志拦截器:

    public class LogInterceptor2 implements okhttp3.Interceptor{
    
            @Override
            public Response intercept(Chain chain) throws IOException {
                //此处getRequestParams是伪代码,仅做示意
                Request request = chain.request();
                Map<String,String> requestParams = request.getRequestParams();
                Response response= chain.proceed(request);
                //此处getResult是伪代码,仅做示意
                String result = response.getResult();
                return response;
            }
        }
    

    可以看到代码几乎一模一样,只是一个直接调用interceptor实现,一个通过chain来实现的,然而区别就在于这个chain.proceed()方法中:

    public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
          RealConnection connection) throws IOException {
        if (index >= interceptors.size()) throw new AssertionError();
    
        calls++;
    
        // If we already have a stream, confirm that the incoming request will use it.
        if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
          throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
              + " must retain the same host and port");
        }
    
        // If we already have a stream, confirm that this is the only call to chain.proceed().
        if (this.httpCodec != null && calls > 1) {
          throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
              + " must call proceed() exactly once");
        }
    
        // 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);
        Response response = interceptor.intercept(next);
    
        // Confirm that the next interceptor made its required call to chain.proceed().
        if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
          throw new IllegalStateException("network interceptor " + interceptor
              + " must call proceed() exactly once");
        }
    
        // Confirm that the intercepted response isn't null.
        if (response == null) {
          throw new NullPointerException("interceptor " + interceptor + " returned null");
        }
    
        if (response.body() == null) {
          throw new IllegalStateException(
              "interceptor " + interceptor + " returned a response with no body");
        }
    
        return response;
      }
    

    总体看下来,本质区别其实就一个,okhttp的proceed()方法会被执行多次(在每个interceptor中都会调用chain.proceed()获取Response),每一个interceptor都会执行该方法,所以在该方法中我们可以对interceptor的实现做很多检查,做出一些约束。然而我们自己DIY的结构,我们最理想的也只是把这些检查封装成一种util,由interceptor的实现人员手动调用,然而靠interceptor开发人员调用的话很容易遗漏,所以这里应该是借用了代理的思想,将它封装在了chain中,必须通过chain的proceed()方法获取Response,这些就能由框架做这些检查和约束了。

    总结

    okhttp的这套责任链模式的实现,从代码上来看确实复杂了不少,理解起来没那么容易,但是,这套模式能够对每个interceptor的行为做出一些基本的规范和检查,而不是都扔给interceptor的实现人员去做,这点收益就大了,稍微难理解一点也是可以的,当然我们更应该学习的是这种将约束和检查收敛到框架中的这种想和实现思路,比如这里就打破了常规的责任链,利用了代理的思想引入了一个chain。
    最后,这些都是我自己揣测的,如果有错误,希望大家指出,共同进步

    相关文章

      网友评论

        本文标题:OkHttp源码之责任链模式

        本文链接:https://www.haomeiwen.com/subject/nzhroftx.html