架构设计之美-揭秘Retrofit设计原理

阅读 331
收藏 6
2019-05-22
原文链接:jakeprim.cn

架构设计之美-揭秘Retrofit设计原理

Retrofit是Android开发中主流的网络请求框架,为什么Retrofit会成为主流? Retrofit 解决了什么样的问题? Retrofit的目的是什么? 以及Retrofit是如何设计网络请求框架的.等等 我们不得不去了解Retrofit的设计原理.

我会通过一下几个部分进行全面的讲解Retrofit

image.png

image.png

注意 注意 注意 重要的事情说三遍:阅读本文, 需要准备的工具 IntelliJ IDEA,下载Retrofit 源码 .使用IDEA 打开Retrofit源码并且能运行samples module下的SimpleService.这是阅读本文的必要条件.

配置samples module 下的pom.xml文件,添加如下代码

<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.sourceEncoding>UTF-8</project.reporting.sourceEncoding>

		<!-- Compilation -->
		<java.version>1.8</java.version>
		<kotlin.version>1.3.10</kotlin.version>
	</properties>
<dependencies>
<dependency>
			<groupId>org.jetbrains.kotlin</groupId>
			<artifactId>kotlin-stdlib</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.jetbrains.kotlinx</groupId>
			<artifactId>kotlinx-coroutines-core</artifactId>
			<optional>true</optional>
		</dependency>
  <dependency>
			<groupId>com.squareup.okhttp3</groupId>
			<artifactId>logging-interceptor</artifactId>
			<version>3.10.0</version>
		</dependency>
  ......
  	</dependencies>

Retrofit 背景和目的

image.png

image.png

Retrofit目的就是不需要自己在去写繁琐的网络请求代码,只需要通过接口类添加注解就可以获取到请求的返回值.Retrofit的高度封装同样也会带来扩展性差等缺点,但是Retrofit的优点明显大于缺点,它的易用性和轻量级,满足了大部分App的网络请求,同时通过注解非常快的实现网络请求,提高了编程的效率,这也是Retrofit为什么这么火的原因.

如何使用Retrofit

至于如何使用Retrofit这里就不展开篇幅去讲了.官方的文档有详细的讲解和源码中的demo都有讲解如何使用Retrofit

官方文档)

其他的博客文章介绍如何使用

Android Retrofit 2.0 的详细 使用攻略(含实例讲解)

源码分析

接口的实现类从哪来的?

Retrofit 使用时必须要定义一个接口类,通过注解注入要请求的URL和参数值,如下示例,@GET(…..) 通过get方式请求网络,Retrofit会将接口生成实例化的对象,接口是无法生成实例化对象的,必须要实现接口,那么接口的实现类是从哪来的呢?

public interface GitHub {
        @GET("/repos/{owner}/{repo}/contributors")
        Call<List<Contributor>> contributorss(
                @Path("owner") String owner,
                @Path("repo") String repo,
                @Query("current") Date now);

        @GET("/repos/{owner}/{repo}/contributors")
        Call<List<Contributor>> contributors(
                @Path("owner") String owner,
                @Path("repo") String repo);

        //jdk 8 可以在接口中实现方法
//    default void test(){
//
//    }
    }
  Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(API_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        SimpleService.GitHub gitHub = retrofit.create(SimpleService.GitHub.class);

在java 中有一个内置的动态代理机制,通过接口类会自动生成代理对象, 需要注意动态代理必须基于接口类,否则就没有意义了.

在上述代码中retrofit.create(SimpleService.GitHub.class);很明显生成了代理对象,它是如何生成的呢? 下面我们来开展讲述动态代理的实现原理.

动态代理是基于接口实现的,首先需要定义一个接口类,就定义一个IEat接口,可以比喻为定义一个吃货,但是谁是吃货呢? 不是每个人都是吃货,如何去找到这个吃货呢,我们可以去网站(动态代理)上查找,然后网站(动态代理)就会自动给我们返回一个吃货对象,那么这个网站就是动态代理, 不需要我们亲自出去找一个吃货.

public interface IEat {
    void eat();
}

那么这个网站如何找到这个具体的吃货呢? 根据你检索的IEat接口 生成一个吃货对象,这个吃货是谁呢? 原来是只猫 如何生成这只猫呢?

下载.png

下载.png

IEat eat =
                (IEat) Proxy.newProxyInstance(
                        IEat.class.getClassLoader(),
                        new Class[]{IEat.class},
                        new InvocationHandler() {
                            @Override
                            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                                if (method.getName().equals("eat")) {
                                    System.out.println("proxy = [" + proxy + "], method = [" + method + "], args = [" + args + "]");
                                    return null;
                                }
                                //
                                return null;
                            }
                        });

上述代码,自动生成代理对象,所有方法都转发给了InvocationHandler的invoke方法,对接口中的所有方法对其进行处理.

看一下Proxy.newProxyInstance做了什么

.......
        Class<?> cl = getProxyClass0(loader, intfs);

.......
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});

将newProxyInstance方法中的一些判断和异常处理代码去掉,剩下的就是核心代码.

分析一下上述代码,首先 Class<?> cl = getProxyClass0(loader, intfs);看到它想到的就是 生成了一个Class,那么这个Class 是不是我们一直苦苦寻找的接口的实现类呢? 很明显它的怀疑是非常大的,进一步确认来看看getProxyClass0做了什么呢?

 private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

上述代码 从proxyClassCache中获取Class,意图就很明显了,如果存在,就直接从缓存中获取,不存在就生成然后存入缓存,java在这一块做的非常好,这样能够提高很大的性能.那么生成Class 肯定在proxyClassCache里面

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
    proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

在上述代码中,我们先不管WeakCache是什么,先把它看成一个缓存的类,紧接着就会看到ProxyClassFactory,命名非常好,代理的工程,那么它肯定生成了代理类.继续从ProxyClassFactory中查看代码,只要找到那么我们的猜测就成立了.

看下面代码,在apply方法中返回了Class,我们直接看这个方法的最后return

@Override
     public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
      byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
             proxyName, interfaces, accessFlags);
             return defineClass0(loader, proxyName,
                                 proxyClassFile, 0, proxyClassFile.length);
     }

在上述代码中,可以看出ProxyGenerator.generateProxyClass意图很明显生成了class字节码,然后通过字节码生成类.

那么它到底是不是生成了代理类呢? 我已经对它的怀疑度达到了99%,最为一名严谨的学者,对它进行最后的确认,查看生成的类是什么到底有没有实现IEat接口, 将生成class字节码的关键代码拿出来,生成.class文件.

byte[] myProxies = ProxyGenerator.generateProxyClass("MyProxy",
                new Class[]{IEat.class}, Modifier.FINAL | Modifier.PUBLIC);
        FilesKt.writeBytes(new File("MyProxy.class"), myProxies);

运行上述代码,会在你的项目中生成一个MyProxy.class文件,来看一下这个文件到底是什么,看下面代码,MyProxy 继承了Proxy 实现了IEat 接口,没错最后的1%的疑虑已经消失了,100%可以确定,上面的代码就是生成代理类的核心代码.也是真正的凶手(PS:程序界的侦探柯南)

public final class MyProxy extends Proxy implements IEat {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public MyProxy(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void eat() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.example.retrofit.proxy.IEat").getMethod("eat");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

我们来分析一下生成的代理类.主要看它如何实现接口的方法,eat()方法交给了invoke处理,再一次证明了动态代理的机制.

public final void eat() throws  {
       try {
           super.h.invoke(this, m3, (Object[])null);
       } catch (RuntimeException | Error var2) {
           throw var2;
       } catch (Throwable var3) {
           throw new UndeclaredThrowableException(var3);
       }
   }

既然我们已经知道了,如何实现代理类了,那么如何实现类的实例就很简单了,当然是使用反射了,再回到Proxy.newProxyInstance方法,下面的代码也就没啥可说的了,java的基础知识,不知道Google一下就懂了

final Constructor<?> cons = cl.getConstructor(constructorParams);
           final InvocationHandler ih = h;
           if (!Modifier.isPublic(cl.getModifiers())) {
               AccessController.doPrivileged(new PrivilegedAction<Void>() {
                   public Void run() {
                       cons.setAccessible(true);
                       return null;
                   }
               });
           }
           return cons.newInstance(new Object[]{h});

如果你完整的看完了上面的讲解,那么动态代理的原理你就很清楚了. 又get到了一个技能点.

Retrofit 为什么使用动态代理呢? 动态代理解决了哪些痛点?(程序员就像侦探不断的探索深入才能找到真正的幕后人)

自动生成代理对象,所有方法都转发给了InvocationHandler的invoke方法使其对接口中的所有方法对其统一进行处理,这样做的好处就是,不用自己在生成实现类, 构建请求、参数编码、数据解析等复杂重复的任务,只需要一个接口就可以优雅的解决这些痛点

代理对象是在程序运行时产生的.class文件,不是编译期,这也提高了一些性能.

看一下Retrofit 如何使用动态代理

public <T> T create(final Class<T> service) {
       //动态代理 动态创建一个代理对象,该对象的所有方法都会转发给InvocationHandler Class<?> cl = getProxyClass0(loader, intfs);
       return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service},
               new InvocationHandler() {
                   private final Platform platform = Platform.get();
                   private final Object[] emptyArgs = new Object[0];

                   @Override
                   public @Nullable
                   Object invoke(Object proxy, Method method,
                                 @Nullable Object[] args) throws Throwable {
                       // If the method is a method from Object then defer to normal invocation.
                       System.out.println("method = [" + method.getDeclaringClass() + "]");
                       //如果传入的类它不是一个接口就直接调用 该方法不去处理
                       if (method.getDeclaringClass() == Object.class) {
                           return method.invoke(this, args);
                       }
                       //java 8 可以定义默认的方法 直接调用默认的方法
                       if (platform.isDefaultMethod(method)) {
                           return platform.invokeDefaultMethod(method, service, proxy, args);
                       }
                       //args 方法的参数
                       System.out.println("invoke: " + args[0] + " proxy:" + proxy.toString());
                       //加载service的方法 处理当前方法和注解 开始网络请求的核心代码
                       //需要注意返回值的处理
                       return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
                   }
               });
   }

从上述代码中,Retrofit 将接口的方法交给loadServiceMethod去处理,如何处理我会在后面讲解,还可以看到Retrofit对细节做的非常好,我相信设计Retrofit的都是处女座的程序员,看一下细节代码,对传入的类进行了类型判断如果不是接口类,交给该类的方法自己处理,还有一个细节,java 8可以在接口中定义默认方法.考虑到了很多情况,这也是非常值得学习的地方.

//如果传入的类它不是一个接口就直接调用 该方法不去处理
                       if (method.getDeclaringClass() == Object.class) {
                           return method.invoke(this, args);
                       }
                       //java 8 可以定义默认的方法 直接调用默认的方法
                       if (platform.isDefaultMethod(method)) {
                           return platform.invokeDefaultMethod(method, service, proxy, args);
                       }

学以致用-哪些场景还可以使用动态代理

比如Android的插件化技术 就有用到动态代理

比如RPC框架,Spring AOP机制

比如处理多个接口,同一复杂逻辑统一进行处理封装

等等…. 你可以想想在你的项目中是否可以用动态代理解决某些复杂的逻辑呢?

如果你想到了可以在下方留言,让大家都看看动态代理还可以解决哪些问题?

举一反三-动态代理的其他实现方案

Java中还有一个库,也实现了动态代理CGLB

看看如何使用CGLB,首先在pom.xml文件进入CGLB配置

<dependency>
			<groupId>cglib</groupId>
			<artifactId>cglib</artifactId>
			<version>3.2.10</version>
		</dependency>

CGLB 不仅可以代理接口类也可以代理普通类,关于CGLB的原理这里就不展开篇幅去讲解了,因为代理的原理其实都是一样的如果你有兴趣的话可以去CGLB原理)看

看下面代码,CGLB的使用更加人性化,同样也是通过invoke去处理方法的.

Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TigerEat.class);
        enhancer.setInterfaces(new Class[]{SimpleService.GitHub.class, Runnable.class});
        enhancer.setCallback(new InvocationHandler() {

            @Override
            public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                System.out.println("before");

                System.out.println("after");
                return null;
            }
        });

那么问题来了,既然还有其他动态代理方案,Retrofit为什么不使用更人性化功能更加强大的CGLB呢? 我个人认为Retrofit的设计是为了易用性、轻量级、安全性设计的.Java JDK 自带的动态代理完全可以实现易用性和轻量级,完全没有必要引入几百K的CGLB包. 从中可以看到一个框架的设计是会进行各方面考虑的,框架的设计一定是易用和轻量的. 这也是为什么做一个架构师是非常不好做的,全面的考虑才是一个合格的架构.

Retrofit如何处理网络请求

上述我们知道了Retrofit如何实现了接口类,那么它是如何调用接口类的方法去配置参数,请求网络呢? 根据下面的流程图具体的进行分析

流程图如下

image.png

image.png

代码分析

我们根据流程图来分析代码

Retrofit通过Java动态代理的invoke方法 调用了loadServiceMethod方法去处理配置参数和网络请求.

loadServiceMethod的代码如下:

ServiceMethod<?> loadServiceMethod(Method method) {
        //从map 中获取是否存储了该方法 提高性能serviceMethodCache 必定是线程安全的ConcurrentHashMap 避免多个同时请求
        ServiceMethod<?> result = serviceMethodCache.get(method);
        if (result != null) return result;

        synchronized (serviceMethodCache) {//加锁 进一步确保线程安全
            result = serviceMethodCache.get(method);
            if (result == null) {//不存在当前的存储的方法
                //解析当前的方法
                result = ServiceMethod.parseAnnotations(this, method);
                //存储当前的方法 下次就不用再去解析 直接获取
                serviceMethodCache.put(method, result);
            }
        }
        return result;
    }

分析一下上述代码,首先从缓存中通过method serviceMethodCache.get(method)获取ServiceMethod可以将ServiceMethod理解为配置好的请求参数、URL和请求配置,下次在请求该方法可直接从缓存中获取,而不用再去解析设置一篇,可以有效的提高性能.serviceMethodCache 是一个ConcurrentHashMap 可以保证同时请求多个接口,线程安全. 从这段代码,可以看到Retrofit不仅考虑到安全性的问题,还考虑到了如何提高性能和代码的执行效率,这些都是需要学习的点. 阅读优秀的代码可以开阔自己的视野.

ServiceMethod.parseAnnotations(this, method); 这段代码很明显就是解析方法的注解并装配参数设置请求,下面看一下如何具体实现的.

如何解析并配置参数

//解析方法的注解
    static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
        //通过RequestFactory 解析方法的注解和参数的注解 配置请求参数
        RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);
........... //去掉异常处理代码 去看核心代码
        //
        return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
    }

从上述代码来看,RequestFactory很显然它是包装了参数和请求,parseAnnotations方法来看一下他是如何进行解析的.

static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
    return new Builder(retrofit, method).build();
  }
RequestFactory build() {
      //遍历方法的注解 进行解析 比如:/repos/{owner}/{repo}/contributors
      for (Annotation annotation : methodAnnotations) {
        //解析方法的注解
        parseMethodAnnotation(annotation);
      }
    .........
      int parameterCount = parameterAnnotationsArray.length;//获取参数注解的数量
      parameterHandlers = new ParameterHandler<?>[parameterCount];//创建参数处理类 数组
      for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
        //解析参数
        parameterHandlers[p] =
            parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
      }
      ........
      return new RequestFactory(this);
    }

上述代码,其实是非常简单的,parseMethodAnnotation去解析方法的注解,parseParameter是解析参数和参数的注解.下面来看一下如何进行具体的解析的.

parseMethodAnnotation,很显然这个方法就是匹配了注解的类型,真正的解析在parseHttpMethodAndPath方法中,这里我们以GET请求的方式为例

private void parseMethodAnnotation(Annotation annotation) {
      //匹配注解
      if (annotation instanceof DELETE) {
        parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
      } else if (annotation instanceof GET) {//以GET 请求方式为例
        //解析注解值value
        parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
      } 
      ......
    }

parseHttpMethodAndPath,看下面的代码,主要逻辑就是将请求方式和URL赋值存储,需要注意的是relativeUrlParamNames存储了,需要传递值的参数名,这个很有用会在后面中用到.parsePathParameters其实就是解析URL地址,拿到参数名,例如:”/repos/{owner}/{repo}/contributors” 通过 Pattern.compile("\\{(" + PARAM + ")\\}");解析到ownerrepo存储到一个Set的结构中.

private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
      ........
      this.httpMethod = httpMethod;//赋值请求方式 比如:GET
      this.hasBody = hasBody;
      if (value.isEmpty()) {
        return;
      }
      // Get the relative URL path and existing query string, if present.
      int question = value.indexOf('?');//判断url 是否为:/repos/?{owner} 样式
      if (question != -1 && question < value.length() - 1) {
        // Ensure the query string does not have any named parameters.
        String queryParams = value.substring(question + 1);//queryParams = {owner}
        Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(queryParams);//
        if (queryParamMatcher.find()) {
          throw methodError(method, "URL query string \"%s\" must not have replace block. "
              + "For dynamic query parameters use @Query.", queryParams);
        }
      }
      this.relativeUrl = value;//得到真正请求的url
      //获取给定URI中使用的唯一路径参数集。如果参数在URI中使用了两次,则它只会在集合中显示一次
      this.relativeUrlParamNames = parsePathParameters(value);//解析URL需要传递参数值的参数名 例如:"owner" "repo" 存在set中
    }

上述的代码就将方法的注解解析完毕了.代码很简单没有复杂的设计,相信大家都能看懂,下面来看如何去解析参数. 回到下面的代码中,parameterCount其实就是动态代理传递过来参数数组的大小,new Builder时就进行了一些赋值,parameterHandlers是一个ParameterHandler包装成为一个参数处理数组,这个类我们后面再讲,先知道有这么一个东西.

 int parameterCount = parameterAnnotationsArray.length;//获取参数注解的数量
      parameterHandlers = new ParameterHandler<?>[parameterCount];//创建参数处理类 数组
      for (int p = 0, lastParameter = parameterCount - 1; p < parameterCount; p++) {
        //解析参数
        parameterHandlers[p] =
            parseParameter(p, parameterTypes[p], parameterAnnotationsArray[p], p == lastParameter);
      }

Builder(Retrofit retrofit, Method method) {
      this.retrofit = retrofit;
      this.method = method;//方法
      this.methodAnnotations = method.getAnnotations();//获取方法的注解
      this.parameterTypes = method.getGenericParameterTypes();//参数的类型
      this.parameterAnnotationsArray = method.getParameterAnnotations();//获取参数的注解
    }

继续点击parseParameter,又调用了parseParameterAnnotation去解析参数注解.

private @Nullable ParameterHandler<?> parseParameter(
       int p, Type parameterType, @Nullable Annotation[] annotations, boolean allowContinuation) {
     ParameterHandler<?> result = null;
     if (annotations != null) {
       for (Annotation annotation : annotations) {
         ParameterHandler<?> annotationAction =//获得参数处理对象
             parseParameterAnnotation(p, parameterType, annotations, annotation);//解析参数注解 p:第几个参数 注解类型 注解数组 当前的注解
        ..........
         result = annotationAction;
       }
     }
     ......
     return result;
   }

parseParameterAnnotation方法代码如下,首先匹配注解类型,这里以Path类型为例,代码如下,获取注解的值,例如:@Path(“owner”) 获取到的是owner,然后判断url中是否存在相同的参数名,在上述解析方法中会得到一个relativeUrlParamNames它存储了URL中的参数名,这里进一步判断参数注解的参数名和URL中的参数名是否一致relativeUrlParamNames.contains(name),如果不一致就会抛出异常提示用户,最后调用了stringConverter这个是数据转换类,我会在后面讲解,这个stringConverter它会对URL进行处理,例如传递Date参数,我们可以自定义Converter将Data格式化处理转成字符串传递,否则直接传递Date会是乱码.关于自定义Converter我会在后面详细的讲解.代码的最后是返回了一个ParameterHandler,这个类是很重要的它承担了URL和参数装配在一起的真实的URL

if (annotation instanceof Path) {//以Path为例
        validateResolvableType(p, type);//判断是否有不支持的参数类型 例子为String 支持的类型
        //判断是否存在 其他注解类型 如果存在抛出异常 参数注解只允许有一个注解
        ..............
        gotPath = true;
        Path path = (Path) annotation;
        String name = path.value();//获取注解的值
        validatePathName(p, name);//确认注解中的值是否和请求URL的参数名是否一致
        Converter<?, String> converter = retrofit.stringConverter(type, annotations);//类型转换 对URL进行处理 比如打印URL
        return new ParameterHandler.Path<>(method, p, name, converter, path.encoded());

      }

我们来看一下ParameterHandler.Path方法,代码如下,通过构造函数进行了一些赋值,valueConverter是对参数值的转换类,apply方法是在请求网络时调用的,它调用了addPathParam方法,需要注意的是不同的参数注解类型,它的装配参数的方式也不同,本例使用了Path为例.

static final class Path<T> extends ParameterHandler<T> {//参数的包装类
    private final Method method;
    private final int p;
    private final String name;
    private final Converter<T, String> valueConverter;
    private final boolean encoded;


    Path(Method method, int p, String name, Converter<T, String> valueConverter, boolean encoded) {
      this.method = method;
      this.p = p;
      this.name = checkNotNull(name, "name == null");
      this.valueConverter = valueConverter;
      this.encoded = encoded;
    }

    @Override void apply(RequestBuilder builder, @Nullable T value) throws IOException {
      if (value == null) {
        throw Utils.parameterError(method, p,
                "Path parameter \"" + name + "\" value must not be null.");
      }
      //拼接 URL和参数值
      builder.addPathParam(name, valueConverter.convert(value), encoded);
    }
  }

我们看一下RequestBuilder的addPathParam方法,代码如下,如何拼接请求的URL,原来如此,例如我们的URL是这种格式的”/repos/{owner}/{repo}/contributors” 最后拼接为:“/repos/square/retrofit/contributors” 将{….}的替换为具体的值,生成最终请求的URL.Retrofit参数的注解类型有好多种,大致上逻辑是相同的,感兴趣的可以去看其他注解类型的处理逻辑.

//将请求的URL 和参数名 参数值 进行拼接
void addPathParam(String name, String value, boolean encoded) {
  if (relativeUrl == null) {
    // The relative URL is cleared when the first query parameter is set.
    throw new AssertionError();
  }
  String replacement = canonicalizeForPath(value, encoded);
  String newRelativeUrl = relativeUrl.replace("{" + name + "}", replacement);
  if (PATH_TRAVERSAL.matcher(newRelativeUrl).matches()) {
    throw new IllegalArgumentException(
        "@Path parameters shouldn't perform path traversal ('.' or '..'): " + value);
  }
  //将拼接的最终URL 赋给relativeUrl
  relativeUrl = newRelativeUrl;
}

分析到此,我们已经大致清晰的知道了Retrofit是如何装配参数.并生成真正的请求URL.下面来分析如何去请求网络并将URL、请求方式、请求头配置给OkHttp的.

如何配置请求

说到配置请求网络,我们要回到原点,原点是哪呢?retrofit.create

@Override
                   public @Nullable
                   Object invoke(Object proxy, Method method,
                                 @Nullable Object[] args) throws Throwable {
                  .......
                       //加载service的方法 处理当前方法和注解 开始网络请求的核心代码
                       //需要注意返回值的处理
                       return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
                   }

这段代码看了无数次了,很显然loadServiceMethod是装配参数生成请求的URL,invoke方法开始请求网络.从下面的代码中,可以看出parseAnnotations返回的是HttpServiceMethod,那么HttpServiceMethod肯定继承了ServiceMethod,invoke方法是抽象的,那么HttpServiceMethod一定实现了invoke方法,很巧妙的设计思路.

abstract class ServiceMethod<T> {
    //解析方法的注解
    static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
       ...........
        //
        return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
    }

    abstract @Nullable
    T invoke(Object[] args);
}

看一下 invoke的实现,OkHttpCall 没错他就是配置网络请求,传递的参数可能看着有点懵,现在只需要关注requestFactory,就可以了这个就是前面将的对参数进行配置,现在只需要关注如何配置网络请求的.

@Override
    final @Nullable
    ReturnT invoke(Object[] args) {
        //配置okhttp 网络请求
        Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
        return adapt(call, args);
    }

点进OkHttpCall,找到enqueue方法,如果你熟悉Okhttp的源码,你一定知道enqueue方法它是发起异步请求,如果你不熟悉也没关系,不影响阅读,如果你有探索的精神可以去看OkHttp源码解析及OkHttp的设计思想.

OkHttpCall的构造方法中,并没有配置网络请求,那么它一定在调用的方法中进行了配置,在下面代码中,我找到了createRawCall这个方法,我猜测他有88%的可能就是配置网络请求,为了验证猜测我们点进去看一下.

//异步请求
  @Override public void enqueue(final Callback<T> callback) {
    checkNotNull(callback, "callback == null");

    okhttp3.Call call;
    Throwable failure;

    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = rawCall;
      failure = creationFailure;
      if (call == null && failure == null) {
        try {
          call = rawCall = createRawCall();//创建call
        } catch (Throwable t) {
          throwIfFatal(t);
          failure = creationFailure = t;
        }
      }
    }

    if (failure != null) {
      callback.onFailure(this, failure);
      return;
    }

    if (canceled) {
      call.cancel();
    }

createRawCall的代码如下,通过callFactory工厂newCall,callFactory是OkHttp中的代码,我会在后面讲解它的作用,requestFactory.create(args)这句话非常可疑,可疑度增加到了98%,点进这个方法来看一下.

private okhttp3.Call createRawCall() throws IOException {
  okhttp3.Call call = callFactory.newCall(requestFactory.create(args));
  if (call == null) {
    throw new NullPointerException("Call.Factory returned null.");
  }
  return call;
}

requestFactory.create(args)它到底做了什么呢? 请求的URL到底在哪里配置给了OkHttp呢? 真凶马上要呼之欲出了(PS:柯南朝你射了一针…..)

image.png

image.png

//创建真正的执行请求 将请求参数和URL 交给OkHttp处理
 okhttp3.Request create(Object[] args) throws IOException {
   @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
   ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
   int argumentCount = args.length;//参数的个数 args是参数数组
   ........ 去除异常的干扰
   RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl,
       headers, contentType, hasBody, isFormEncoded, isMultipart);
   .........
   List<Object> argumentList = new ArrayList<>(argumentCount);
   for (int p = 0; p < argumentCount; p++) {
     argumentList.add(args[p]);
     handlers[p].apply(requestBuilder, args[p]);//处理参数值 和URL进行拼接 并存储在requestBuilder中
   }
   return requestBuilder.get()
       .tag(Invocation.class, new Invocation(method, argumentList))
       .build();
 }

从上述代码中,首先看到ParameterHandler相信这个类大家很熟悉了,参数的处理类,调用apply来生成真实请求的URL,其实调用了requestBuilder的方法,然后将真实的URL存储在requestBuilder中,然后调用了requestBuilder.get(),在下面代码中,我们终于看到了url = baseUrl.resolve(relativeUrl);然后配置给了okhttp的Request,我们都知道Retrofit请求网络是Okhttp那么它到底是如何设置URL、请求头、请求方式的? 至此所有的真相大白,通过重重迷雾探索到了真相.

Request.Builder get() {
   HttpUrl url;
   HttpUrl.Builder urlBuilder = this.urlBuilder;
   if (urlBuilder != null) {
     url = urlBuilder.build();
   } else {
     url = baseUrl.resolve(relativeUrl);//base url + relativeUrl 为请求的最终的URL
     ......
   }
   //处理body
   RequestBody body = this.body;
   if (body == null) {
     // Try to pull from one of the builders.
     if (formBuilder != null) {
       body = formBuilder.build();
     } else if (multipartBuilder != null) {
       body = multipartBuilder.build();
     } else if (hasBody) {
       // Body is absent, make an empty body.
       body = RequestBody.create(null, new byte[0]);
     }
   }
   //处理contentType
   MediaType contentType = this.contentType;
   if (contentType != null) {
     if (body != null) {
       body = new ContentTypeOverridingRequestBody(body, contentType);
     } else {
       headersBuilder.add("Content-Type", contentType.toString());
     }
   }
   //生成Request
   return requestBuilder
       .url(url)
       .headers(headersBuilder.build())
       .method(method, body);
 }

最后通过流程图来梳理一下,总结一下Retrofit配置请求的过程

image.png

image.png

Retrofit 如何处理返回类型(适配器)和解析响应数据

在上面几节,讲解了动态代理、装配参数、配置请求,Retrofit 还剩余最重要的两部分处理返回类型和解析转换响应数据.

处理返回类型

来看HttpServiceMethod这个类的parseAnnotations方法,代码如下

 Annotation[] annotations = method.getAnnotations();
        //获取返回值的类型
        Type adapterType;
        if (isKotlinSuspendFunction) {
          .........
            }

            adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
            annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
        } else {
            adapterType = method.getGenericReturnType();//获取方法的返回值类型
        }

        //处理返回类型的适配器
        CallAdapter<ResponseT, ReturnT> callAdapter =
                createCallAdapter(retrofit, method, adapterType, annotations);
        //真实对返回类型 例如:Call<List<Contributor>> 返回的是List<Contributor>
        Type responseType = callAdapter.responseType();
       ............
}

上述代码中,通过一个CallAdapter来处理返回类型,CallAdapter其实就是一个接口,createCallAdapter拿到根据不同的返回类型匹配拿到,Retrofit默认的Adapter或自定义的Adapter,可以自定义一个CallAdapter来处理返回类型.CallAdapter一定有一个默认的实现,并且Retrofit会有相关的接口,我们来看一下Retrofit类,代码如下:

final List<CallAdapter.Factory> callAdapterFactories;//call 返回处理工厂
Retrofit(okhttp3.Call.Factory callFactory, HttpUrl baseUrl,
             List<Converter.Factory> converterFactories, List<CallAdapter.Factory> callAdapterFactories,
             @Nullable Executor callbackExecutor, boolean validateEagerly){}
public List<CallAdapter.Factory> callAdapterFactories() {
        return callAdapterFactories;
    }
public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
            callAdapterFactories.add(checkNotNull(factory, "factory == null"));
            return this;
        }
 List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>(this.callAdapterFactories);
//build 方法中            
callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));
//默认的适配器
 List<? extends CallAdapter.Factory> defaultCallAdapterFactories(
      @Nullable Executor callbackExecutor) {
    return singletonList(new DefaultCallAdapterFactory(callbackExecutor));
  }

上述代码,可以看出Retrofit 的构造方法中设置了callAdapterFactories 适配器的一个工厂,Retrofit类还提供了添加适配器工厂的方法,支持自定义的适配器,在Retrofit的build方法中,果然找到了默认的适配器工厂,我们来看一下默认的适配器是如何实现的? 代码如下:

@Override public @Nullable CallAdapter<?, ?> get(
     Type returnType, Annotation[] annotations, Retrofit retrofit) {
   //如果不是Call类型就不进行处理
   if (getRawType(returnType) != Call.class) {//getRawType(returnType) 返回对是去除泛型对类型,例如:Call<List<Contributor>> 返回Call
     return null;
   }
   if (!(returnType instanceof ParameterizedType)) {//returnType = Call<List<Contributor>>
     throw new IllegalArgumentException(
         "Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
   }
   //responseType = List<Contributor> 泛型的类型 可能会存在Call<List<Contributor>,...,...> 多个泛型,这里只处理第一个
   final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);
......
   return new CallAdapter<Object, Call<?>>() {
     @Override public Type responseType() {
       //返回泛型的类型
       return responseType;
     }
     @Override public Call<Object> adapt(Call<Object> call) {
       //将call返回
       return executor == null
           ? call
           : new ExecutorCallbackCall<>(executor, call);
     }
   };
 }

上述代码中,其实就是解析返回的类型并返回,adaptcall返回,从前面代码中,adapt方法里就是去执行网络请求,调用OkHttpCall类的enqueue方法,但是在默认的adapter中并没有调用enqueue去请求网络,而是直接将call返回,交给外部去调用例如:看下面代码,拿到call后自己去执行.

Call<List<Contributor>> call = github.contributors("square", "retrofit");
      List<Contributor> contributors = call.execute().body();

我们再看一下,Retrofit提供的其他类型的Adapter,在源码的retrofit-adapters中随便看一个类,比如Java8CallAdapterFactory,代码如下,adapt方法中,直接调用了call.enqueue请求网络,而不是像默认实现的那样需要自己调用.如果我们不爽Retrofit提供的Adapter,我们完全可以自己去实现一个.

private static final class BodyCallAdapter<R> implements CallAdapter<R, CompletableFuture<R>> {
    private final Type responseType;

    BodyCallAdapter(Type responseType) {
      this.responseType = responseType;
    }

    @Override public Type responseType() {
      return responseType;
    }

    @Override public CompletableFuture<R> adapt(final Call<R> call) {
      final CompletableFuture<R> future = new CompletableFuture<R>() {
        @Override public boolean cancel(boolean mayInterruptIfRunning) {
          if (mayInterruptIfRunning) {
            call.cancel();
          }
          return super.cancel(mayInterruptIfRunning);
        }
      };

      call.enqueue(new Callback<R>() {
        @Override public void onResponse(Call<R> call, Response<R> response) {
          if (response.isSuccessful()) {
            future.complete(response.body());
          } else {
            future.completeExceptionally(new HttpException(response));
          }
        }

        @Override public void onFailure(Call<R> call, Throwable t) {
          future.completeExceptionally(t);
        }
      });

      return future;
    }
  }

如何支持RxJava、Kotlin协程和自定义适配器

Retrofit经常和RxJava封装在一起,那么如何去适配RxJava的返回类型呢? Retrofit已经给我们提供了一个这样的适配器.代码比较多这里就不再这篇文章来讲解了,下一篇文章来单独讲解.(PS:又留了一个坑)

Retrofit使用了哪些设计模式

Retrofit 用到了挺多的设计模式,核心功能的实现主要使用了一下4中设计模式,这4种设计模式就不在本片文章讲解了,不了解的Google一下 网上有太多的文章讲解了,锻炼一下自学能力吧.

Builder模式

工厂默认

适配器模式

代理模式

这篇专题讲解了常用的设计模式

总结

本片文章的目的,旨在讲解Retrofit的设计原理,掌握优秀的代码设计,和代码阅读能力. 明白原理很简单但是如何掌握这种优秀的设计能力才是核心.

最后总结一下,如何去阅读代码

image.png

image.png

最后的最后分享一下,如何形成自己的知识体系,快速的学习能力,养成习惯形成自己的知识体系,有条理的去学习才能获得真正的成长

image.png

image.png

评论