本篇博文所有涉及代碼已上傳至碼雲:https://gitee.com/zhangningke/java-basis
代理模式
代理模式是通過給某個對象提供一個代理對象,并由代理對象控制對原對象的引用。通俗來講,代理模式中的代理對象就像我們生活中常見的中介, 比如你想租房,一般在各種租房軟體上找房子,聯系到的都是中介,而不是房東。
代理模式的目的在于,一方面是通過引用代理對象的方式間接通路目标對象,防止直接通路目标對象給系統代理不必要的複雜性;另一方面是可以通過代理對象對通路進行控制。
代理模式一般包含三個角色:

【抽象角色】指代理角色和真實角色對外提供的公共方法,一般為一個接口,比如租房服務;
【真實角色】需要實作抽象角色接口,定義了真實角色所要實作的業務邏輯,以便供代理角色調用,也就是真正的業務邏輯在此,比如房東可以提供房子給你住。
【代理角色】需要實作抽象角色接口,是真實角色的代理,通過真實角色的業務邏輯方法來實作抽象方法,并可以附加自己的操作(比如中介會擡高房租再租給你),将統一的流程控制都放到代理角色中處理。
靜态代理
在使用靜态代理時,需要定義接口或者父類,被代理對象與代理對象一起實作相同的接口或者是繼承相同的父類。一般來說,代理對象和被代理對象是一對一的關系,當然一個代理對象對應多個被代理對象也是可以的。
/**
* 抽象角色: 定義了服務的接口
*/
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("布置房間,擡高房價!");
}
}
從上面的案例我們可以發現,使用靜态代理時,一對一則會出現靜态代理對象多、代碼量大,進而導緻代碼複雜,可維護性差的問題,一對多則代理對象會出現擴充能力差的問題。
動态代理
動态代理顧名思義是在運作時動态幫我們建立代理類和類執行個體,是以效率肯定會低一些。要完成這個場景,需要運作期動态建立一個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();
}
上圖的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還是報紅。
我們看到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);
}
}
}