天天看點

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);
    }
  }
}