天天看点

Retrofit中的注解、反射与动态代理

本篇博文所有涉及代码已上传至码云:​​https://gitee.com/zhangningke/java-basis​​

代理模式

代理模式是通过给某个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗来讲,代理模式中的代理对象就像我们生活中常见的中介, 比如你想租房,一般在各种租房软件上找房子,联系到的都是中介,而不是房东。

代理模式的目的在于,一方面是通过引用代理对象的方式间接访问目标对象,防止直接访问目标对象给系统代理不必要的复杂性;另一方面是可以通过代理对象对访问进行控制。

代理模式一般包含三个角色:

Retrofit中的注解、反射与动态代理

【抽象角色】指代理角色和真实角色对外提供的公共方法,一般为一个接口,比如租房服务;

【真实角色】需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用,也就是真正的业务逻辑在此,比如房东可以提供房子给你住。

【代理角色】需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作(比如中介会抬高房租再租给你),将统一的流程控制都放到代理角色中处理。

静态代理

在使用静态代理时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同的父类。一般来说,代理对象和被代理对象是一对一的关系,当然一个代理对象对应多个被代理对象也是可以的。

/**
 * 抽象角色: 定义了服务的接口
 */
public interface RentService {
  void rent();
}      
/**
 * 真实角色: 真正提供房子给你住的房东
 */
public class Landlord implements RentService {
  private String name;

  public Landlord(String name) {
    this.name = name;
  }

  @Override
  public void rent() {
    System.out.println("一室一厅,价格美丽,先到先得!");
  }
}      
/**
 * 代理对象:租房服务经纪人
 * 静态代理的代理对象只能为一个接口服务
 */
public class RentAgent implements RentService{
  private final RentService rentService;

  public RentAgent(RentService rentService) {
    this.rentService = rentService;
  }

  @Override
  public void rent() {
    preOperation();
    rentService.rent();
    afterSalesService();
  }

  private void afterSalesService() {
    System.out.println("合同到期,可帮您转租,请交钱,哈哈哈~");
  }

  private void preOperation() {
    System.out.println("布置房间,抬高房价!");
  }
}      
Retrofit中的注解、反射与动态代理

从上面的案例我们可以发现,使用静态代理时,一对一则会出现静态代理对象多、代码量大,从而导致代码复杂,可维护性差的问题,一对多则代理对象会出现扩展能力差的问题。

动态代理

动态代理顾名思义是在运行时动态帮我们创建代理类和类实例,所以效率肯定会低一些。要完成这个场景,需要运行期动态创建一个Class。JDK提供Proxy来帮我们完成这件事情,基本使用如下:

// 2.动态代理
    final Operator xiaoguo = new Operator("小郭同学");
    //参数含义:第一个:ClassLoader;第二个:代理的服务接口的Class数组(JDK实现只能代理接口);第三个:InvocationHandler回调接口。
    Object proxyInstance = Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{RentService.class,
      MessageService.class}, new InvocationHandler() {
      /**
       *
       * @param o 其实就是被代理的对象
       * @param method 被代理对象要回调的方法
       * @param objects 被代理对象要回调的方法包含的参数
       * @return 其实就是被代理的对象
       * @throws Throwable 抛异常,比如回调的方法找不到
       */
      @Override
      public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
        System.out.println("method == " + method);
        if(objects != null){
          for (int i = 0; i < objects.length; i++) {
            System.out.println("第一个参数的内容是 == " + objects[i]);
          }
        }
        //在这里才是真正执行对象方法的地方
        return method.invoke(xiaoguo, objects);;
      }
    });

    RentService rentService = (RentService) proxyInstance;
    rentService.rent();

    MessageService messageService = (MessageService) proxyInstance;
    messageService.sendMessage("恭喜您中奖6800万!");      

事实上,Proxy.newProxyInstance 会创建一个Class,与静态代理不同,这个Class不是由具体的.java源文件编译生成,就是说在磁盘当中不会有真实的Class文件,而是只会在内存中按照Class文件格式生成一个.class文件。

/**
   * 把生成的代理类的class文件输出到指定目录,用于查看和分析
   *
   * @throws IOException 抛出IO异常
   */
  private static void proxy() throws IOException {
    String name = MessageService.class.getName() + "$Proxy0";
    //ProxyClassFactory最终通过生成代理指定接口的Class数据
    byte[] bytes = ProxyGenerator.generateProxyClass(name, new Class[]{MessageService.class});
    FileOutputStream fos = new FileOutputStream("agent/" + name + ".class");
    fos.write(bytes);
    fos.close();
  }      
Retrofit中的注解、反射与动态代理
Retrofit中的注解、反射与动态代理

上图的h其实就是 InvocationHandler 接口,所以我们在使用动态代理时传递的 InvocationHandler 就是一个监听,在代理对象上执行方法,都会由这个监听回调出来。

Retrofit

Retrofit的简单使用

我们在了解一个框架的原理之前,首先要会使用它,下来我们就看看大名鼎鼎的Retrofit是如何为我们所用的。

step1:在官网(​​https://github.com/square/retrofit​​)查看retrofit最新版本,当然也可以使用历史版本,然后在我们的项目的build.gradle添加其依赖。

//Retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.8.1'      

step2:定义服务端的接口配置类:

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Query;

/**
 * 用于测试Retrofit:
 * 所有的网络请求接口都在这里配置,我们这里规定POST请求的方法参数使用@Field注解,GET请求使用@Query注解。
 */
public interface WeatherApi {
  @POST("/v3/weather/weatherInfo")
  @FormUrlEncoded
  Call<ResponseBody> postWeather(@Field("city") String city, @Field("key") String key);


  @GET("/v3/weather/weatherInfo")
  Call<ResponseBody> getWeather(@Query("city") String city, @Query("key") String key);
}      

step3:创建Retrofit实例对象,并调用create方法返回服务端接口配置类的实例对象。

OkHttpClient okHttpClient = new OkHttpClient.Builder().callTimeout(10, TimeUnit.SECONDS).build();
    Retrofit retrofit = new Retrofit.Builder()
      .baseUrl("https://restapi.amap.com")
      .callFactory(okHttpClient)// 可以定制OkHttpClient
      .build();
    mWeatherApi = retrofit.create(WeatherApi.class);      

step4:访问网络请求,其实真正提供访问网络能力的还是okhttp3.Call.Factory,而OkHttpClient是其唯一实现类,提供了同步访问网络请求的execute方法和异步访问网络请求的enqueue方法:

/**
   * 使用Retrofit的Post请求
   */
  private void getWeatherDataByPost() {
    new Thread(() -> {
      Call<ResponseBody> postWeatherCall = mWeatherApi.postWeather("110101", "ae6c53e2186f33bbf240a12d80672d1b");
      try {
        Response<ResponseBody> response = postWeatherCall.execute();
        if (response.isSuccessful()) {
          String weatherData = response.body() == null ? "" : response.body().string();
          Log.d(TAG, "getWeatherDataByPost: weatherData : " + weatherData);
          refreshUI(weatherData, mTVWeatherDataByPost);
        }
      } catch (IOException e) {
        Log.e(TAG, "onFailure: get weather data failed by post.");
      }
    }).start();
  }

  /**
   * 使用Retrofit的Get请求
   */
  private void getWeatherDataByGet() {
    Call<ResponseBody> weatherCall = mWeatherApi.getWeather("110101", "ae6c53e2186f33bbf240a12d80672d1b");
    weatherCall.enqueue(new Callback<ResponseBody>() {
      @Override
      public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        try {
          if (response.isSuccessful()) {
            String weatherData = response.body() == null ? "" : response.body().string();
            Log.d(TAG, "onResponse: weatherData : " + weatherData);
            refreshUI(weatherData, mTVWeatherDataByGet);
          }
        } catch (IOException e) {
          Log.e(TAG, "onFailure: get weather data failed by get,may be IOException.");
        }
      }

      @Override
      public void onFailure(Call<ResponseBody> call, Throwable t) {
        Log.e(TAG, "onFailure: get weather data failed by get.");
      }
    });
  }      

 PS:记得在清单文件里面加网络权限:

<uses-permission android:name="android.permission.INTERNET" />      

实现五毛钱的Retrofit

了解了Retrofit的基本使用,我们打算自己撸一个简易版的Retrofit(不包含适配器和转换器,只提供基本网络访问能力)。

大部分从事安卓开发的同学应用都知道Retrofit是基于注解、反射和动态代理实现的,如果不知道,现在我告诉你,你也知道了>~<

还有基本的网络请求的知识点,比如有 POST、GET等请求方法以及它们的区别。另外我们一定要知道,Retrofit 只是对 OkHttp 做了一层封装,最终的网络访问能力还是 OkHttp 提供的。

开始撸码,我们先定义一个访问服务端的接口:

import com.luffy.annotationdemo.annotation.Field;
import com.luffy.annotationdemo.annotation.GET;
import com.luffy.annotationdemo.annotation.POST;
import com.luffy.annotationdemo.annotation.Query;

import okhttp3.Call;

/**
 * 用于测试我们自己写的MyRetrofit
 * 所有的网络请求接口都在这里配置,我们这里规定POST请求的方法参数使用@Field注解,GET请求使用@Query注解。
 */
public interface MyWeatherApi {
  @POST("/v3/weather/weatherInfo")
  Call postWeather(@Field("city") String city, @Field("key") String key);

  @GET("/v3/weather/weatherInfo")
  Call getWeather(@Query("city") String city, @Query("key") String key);
}      

方法以及方法参数的注解,以POST为例,其他注解大同小异,具体可查看文首码云链接:

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target(METHOD)
@Retention(RUNTIME)
public @interface POST {
  String value() default "";
}      

第二步:我们类比Retrofit的使用,编写如下代码,当然此时写出来的代码好多报红,我们接下来再一一实现相关方法。

MyRetrofit myRetrofit = new MyRetrofit.Builder().baseUrl("https://restapi.amap.com").build();
    mMyWeatherApi = myRetrofit.create(MyWeatherApi.class);      

我们看到new MyRetrofit.Builder(),第一时间应该想到的是构建者模式或者干脆就叫Builder模式。于是我们就撸出了如下代码。

import okhttp3.Call;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;

public class MyRetrofit {
    final Call.Factory callFactory;
    final HttpUrl baseUrl;

    MyRetrofit(Call.Factory callFactory, HttpUrl baseUrl) {
        this.callFactory = callFactory;
        this.baseUrl = baseUrl;
    }

    /**
     * 构建者模式,将一个复杂对象的构建和它的表示分离,可以使使用者不必知道内部组成的细节。
     * 注:当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用构造者模式。
     */
    public static final class Builder {
        // 域名和Factory是我们进行网络请求所必须的成员
        private HttpUrl baseUrl;
        // Okhttp->OkhttClient 是 okhttp3.Call.Factory的唯一实现类,如果我们不对callFactory做定制,它就默认是null,所以调用
        // build构建Retrofit时,一定要对其做判空处理。
        private okhttp3.Call.Factory callFactory;

        public Builder callFactory(okhttp3.Call.Factory factory) {
            this.callFactory = factory;
            return this;
        }

        public Builder baseUrl(String baseUrl) {
            this.baseUrl = HttpUrl.get(baseUrl);
            return this;
        }

        public MyRetrofit build() {
            if (baseUrl == null) {
                throw new IllegalStateException("Base URL required.");
            }
            // 如果callFactory为空,给它一个默认的OkHttpClient
            if (callFactory == null) {
                callFactory = new OkHttpClient();
            }
            return new MyRetrofit(callFactory, baseUrl);
        }
    }
}      

PS:关于设计模式,可以参考一个大佬的博客:​​永不磨灭的设计模式 - ShuSheng007​​

此时,我们写的第一行代码已经OK了,第二行的create还是报红。

Retrofit中的注解、反射与动态代理

我们看到create方法传入接口的Class对象,返回的是接口的 一个实例,我们很容易就想到了动态代理,并且我们定义泛型方法易于扩展。

/**
     * 使用动态代理,代理我们定义的接口服务
     *
     * @param service 接口服务
     * @param <T>     使用泛型,增加代码的扩展性
     * @return 接口服务的代理对象
     */
    @SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety.
    public <T> T create(Class<T> service) {
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, new InvocationHandler() {
            private final Object[] emptyArgs = new Object[0];

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /**
                 * 在这里对注解进行解析,得到请求方式和完成的URL
                 */
                // 如果是Object声明的方法,直接调用即可
                if (method.getDeclaringClass() == Object.class) {
                    return method.invoke(this, args);
                }
                // 如果是我们定义的请求网络的方法,需要解析这个method上所有的注解信息
                ServiceMethod serviceMethod = loadServiceMethod(method);
                // args:方法的所有参数
                return serviceMethod.invoke(args == null ? emptyArgs : args);
            }
        });
    }      

上面代码中涉及到 loadServiceMethod 方法和 ServiceMethod 类,其实我们是把所有注解解析相关的内容都放在 ServiceMethod 中处理了,并且会用Map把已经解析过的注解内容缓存起来,每次调用接口方法的时候,通过 loadServiceMethod 先从缓存中去取,取不到再新建,loadServiceMethod 类比了DLC(双重加锁校验)的实现。

/**
     * 先从缓存里面取 ServiceMethod 对象,取不到再去创建
     *
     * @param method 接口中调用的方法
     * @return 返回ServiceMethod 实例对象
     */
    private ServiceMethod loadServiceMethod(Method method) {
        // 先不上锁,避免synchronized的性能损失
        ServiceMethod result = serviceMethodCache.get(method);
        if (result != null) return result;
        // 多线程下,避免重复解析影响性能和功耗
        synchronized (serviceMethodCache) {
            result = serviceMethodCache.get(method);
            if (result == null) {
                result = new ServiceMethod.Builder(this, method).build();
                serviceMethodCache.put(method, result);
            }
        }
        return result;
    }      

看到 Builder 我们同样想到构建者模式:

import com.luffy.annotationdemo.annotation.Field;
import com.luffy.annotationdemo.annotation.GET;
import com.luffy.annotationdemo.annotation.POST;
import com.luffy.annotationdemo.annotation.Query;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Objects;

import okhttp3.Call;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.Request;

/**
 * 用于解析并记录请求类型、参数、完整URL地址
 */
public class ServiceMethod {
  /**
   * 真正提供网络访问能力的实例对象
   */
  private final Call.Factory callFactory;

  /**
   * 相对路径,比如: /v3/weather/weatherInfo
   */
  private final String relativeUrl;

  /**
   * 用于处理接口中的方法参数
   */
  private final ParameterHandler[] parameterHandler;

  /**
   * 用于创建FormBody
   */
  private FormBody.Builder formBuild;

  /**
   * 域名
   */
  HttpUrl baseUrl;

  /**
   * 用于记录请求方式
   */
  String httpMethod;

  /**
   * 用于构建完整的URL
   */
  HttpUrl.Builder urlBuilder;

  public ServiceMethod(Builder builder) {
    if (builder == null) {
      throw new IllegalArgumentException("builder is null.");
    }
    callFactory = builder.myRetrofit.callFactory;
    relativeUrl = builder.relativeUrl;
    parameterHandler = builder.parameterHandler;
    baseUrl = builder.myRetrofit.baseUrl;
    httpMethod = builder.httpMethod;
    /*
     * 如果是有请求体,创建一个 okhttp 的请求体对象(POST请求会有请求体)
     */
    if (builder.hasBody) {
      formBuild = new FormBody.Builder();
    }
  }

  public Object invoke(Object[] args) {
    // 1.处理请求的地址与参数
    for (int i = 0; i < parameterHandler.length; i++) {
      ParameterHandler handlers = parameterHandler[i];
      // handler内本来就记录了key,现在给到对应的value
      handlers.apply(this, args[i].toString());
    }
    // 2.获取最终请求地址
    HttpUrl url;
    if (urlBuilder == null) {
      urlBuilder = baseUrl.newBuilder(relativeUrl);
    }
    url = urlBuilder != null ? urlBuilder.build() : null;
    // 3.请求体,只有Post请求的时候 formBuild 不为空,ServiceMethod 构造函数已经有相关逻辑
    FormBody formBody = null;
    if (formBuild != null) {
      formBody = formBuild.build();
    }
    Request request = new Request.Builder().url(Objects.requireNonNull(url)).method(httpMethod, formBody).build();
    return callFactory.newCall(request);
  }

  /**
   * Post请求:把 k-v 放到请求体中
   *
   * @param key   服务端规定的key
   * @param value 客户端传给服务端的值
   */
  public void addFiledParameter(String key, String value) {
    formBuild.add(key, value);
  }

  /**
   * Get请求:把 k-v 拼到url里面
   *
   * @param key   服务端规定的key
   * @param value 客户端传给服务端的值
   */
  public void addQueryParameter(String key, String value) {
    if (urlBuilder == null) {
      urlBuilder = baseUrl.newBuilder(relativeUrl);
    }
    Objects.requireNonNull(urlBuilder).addQueryParameter(key, value);
  }

  public static class Builder {

    private final MyRetrofit myRetrofit;
    private final Annotation[] methodAnnotations;
    private final Annotation[][] parameterAnnotations;
    ParameterHandler[] parameterHandler;
    private String httpMethod;
    private String relativeUrl;
    private boolean hasBody;

    public Builder(MyRetrofit myRetrofit, Method method) {
      this.myRetrofit = myRetrofit;
      // 获取方法上的所有的注解
      methodAnnotations = method.getAnnotations();
      // 获得方法参数的所有的注解 (之所以是二维数组,是因为一个参数可以有多个注解,一个方法又会有多个参数)
      parameterAnnotations = method.getParameterAnnotations();
    }

    public ServiceMethod build() {
      /*
       * 1.解析方法上的注解, 只处理POST与GET
       */
      for (Annotation methodAnnotation : methodAnnotations) {
        if (methodAnnotation.getClass().isAssignableFrom(POST.class)) {
          // 1.1记录当前请求方式
          this.httpMethod = "POST";
          // 1.2记录请求url的path
          this.relativeUrl = ((POST) methodAnnotation).value();
          // 1.3是否有请求体
          this.hasBody = true;
        } else if (methodAnnotation instanceof GET) {
          this.httpMethod = "GET";
          this.relativeUrl = ((GET) methodAnnotation).value();
          this.hasBody = false;
        }
      }

      /*
       * 2 解析方法参数的注解
       */
      int length = parameterAnnotations.length;
      parameterHandler = new ParameterHandler[length];
      for (int i = 0; i < length; i++) {
        // 2.1拿到第i个参数上的所有的注解
        Annotation[] annotations = parameterAnnotations[i];
        // 2.2处理参数上的每一个注解
        for (Annotation annotation : annotations) {
          if (httpMethod.equals("POST")) {
            if (annotation instanceof Field) {
              //得到注解上的value: 也就是请求参数的key
              String value = ((Field) annotation).value();
              parameterHandler[i] = new ParameterHandler.FiledParameterHandler(value);
            }
            // 这里判断:如果httpMethod是post请求,并且解析到Query注解,可以提示使用者使用Field注解
            else if (annotation instanceof Query) {
              throw new IllegalArgumentException("POST request must use @Field.");
            }
          } else if (httpMethod.equals("GET")) {
            if (annotation instanceof Query) {
              String value = ((Query) annotation).value();
              parameterHandler[i] = new ParameterHandler.QueryParameterHandler(value);
            }
            // 这里判断:如果httpMethod是get请求,并且解析到Field注解,可以提示使用者使用Query注解
            else if (annotation instanceof Field) {
              throw new IllegalArgumentException("GET request must use @Query.");
            }
          }
        }
      }
      return new ServiceMethod(this);
    }
  }
}