Retrofit通過注解的方法標記HTTP請求參數,支持常用HTTP方法,統一返回值解析,支持異步/同步的請求方式,將HTTP請求對象化,參數化。真正執行網絡訪問的是Okhttp,Okhttp支持HTTP&HTTP2,因此,使用Retrofit可以支持REST、HTTPS及SPDY。
行業內分析Retrofit的使用方法的文章已經比較豐富,這里不再贅述,如想了解這部分內容,請參考如下鏈接。
本文主要從設計模式的角度分享對Retrofit源碼的一些理解。
- 外觀模式
- 建造者模式
- 代理模式
- 簡單工廠模式
- 工廠模式
- 抽象工廠模式
一、外觀模式
在封裝某些特定功能的子系統時,外觀模式是一種很好的設計規范。即該子系統的外部與內部通信時通過一個統一的對象進行。Retrofit是整個庫的一個入口類,Retrofit庫的使用基本都是圍繞着這個類。外觀模式具有高內聚、低耦合的特性,對外提供簡單統一的接口,隱蔽了子系統具體的實現、隔離變化。
Retrofit的外觀模式的UML類圖如下所示。
Retrofit對客戶端模塊(Client1、Client2……)提供統一接口,Retrofit類內部封裝了ServiceMethod、CallAdapter和Converter等組件。並且,CallAdapter和Converter都是抽象為接口,用戶可以擴展自定義的實現。正如官方文檔中的示例:
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .build();
附:舉個栗子,說明下外觀模式在封裝條形碼/二維碼掃描功能時的應用。
Android中的條形碼/二維碼掃描功能通常會基於zxing庫進行封裝,定義CaptureActivity,統一提供掃碼功能,並返回掃描結果。客戶端使用該封裝只需兩步:首先,通過Intent啟動CaptureActivity;然后,在onActivityResult中處理掃描結果。
Intent intent = new Intent(MainActivity.this, CaptureActivity.class); startActivityForResult(intent, REQUESTCODE);
if (requestCode == REQUESTCODE && resultCode == RESULT_OK) { Bundle bundle = data.getExtras(); String scanResult = bundle.getString(CaptureActivity.RESULT); helloWorld.setText(scanResult); }
客戶端不需要處理任何跟攝像頭控制、調焦、圖片處理、條形碼解析等相關的問題。CaptureActivity提供了所有掃描相關的功能。
二、建造者模式
設計模式分為三種類型:創建型模式、結構型模式和行為型模式。建造者模式屬於創建型模式,將構建復雜對象的過程和它的部件解耦,使構建過程和部件的表示隔離。Retrofit內部包含Retrofit.Builder,Retrofit包含的域都能通過Builder進行構建。經典設計模式(《設計模式:可復用面向對象軟件的基礎》)中建造者模式有四種角色:
- Product產品類——該類為一般為抽象類,定義Product的公共屬性配置;
- Builder建造類——該類同樣為抽象類,規范Product的組建,一般由子類實現具體Product的構建過程;
- ConcreteBuilder實際建造類——繼承自Builder,構建具體的Product;
- Director組裝類——統一組裝過程。
在Retrofit類中,Retrofit直接對應Product,並沒有基於抽象Product進行擴展;Retrofit.Builder對應ConcreteBuilder,也沒有基於抽象Builder進行擴展,同時省略了Director,並在Retrofit.Builder每個setter方法都返回自身,使得客戶端代碼可以鏈式調用,整個構建過程更加簡單。
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .build();
舉個栗子:筆者基於Retrofit封裝適合自身業務的庫時,由於需要配置基礎URL、默認超時時間、攔截器以及是否添加轉換器等,也采用相同的建造者模式。
public class NetWork { // 基礎URL設置 private String baseUrl; // 默認超時時間 private long timeout; /** * NetWork構建者 */ public static class Builder { // 基礎URL設置 private String baseUrl; // 默認超時時間 private long timeout = 5; /** * baseUrl為必填項 * * @param baseUrl 基礎Url */ public Builder(String baseUrl) { this.baseUrl = baseUrl; } public Builder timeout(long timeout) { this.timeout = timeout; return this; } public NetWork build() { return new NetWork(this); } } /** * 構造器 * * @param builder 構造builder */ private NetWork(Builder builder) { this.baseUrl = builder.baseUrl; this.timeout = builder.timeout; }
三、代理模式
代理模式屬於上述提到的結構型模式。當無法或不想直接訪問某個對象,或者訪問某個對象比較復雜的時候,可以通過一個代理對象來間接訪問,代理對象向客戶端提供和真實對象同樣的接口功能。經典設計模式中,代理模式有四種角色:
- Subject抽象主題類——申明代理對象和真實對象共同的接口方法;
- RealSubject真實主題類——實現了Subject接口,真實執行業務邏輯的地方;
- ProxySubject代理類——實現了Subject接口,持有對RealSubject的引用,在實現的接口方法中調用RealSubject中相應的方法執行;
- Cliect客戶端類——使用代理對象的類。
代理模式分為靜態代理和動態代理,嚴格按照上述角色定義編寫的代碼屬於靜態代理,即在代碼運行前ProxySubject代理類的class編譯文件就已存在。Retrofit使用的是動態代理,是通過反射機制來動態生成方法接口的代理對象的。動態代理的實現是通過JDK提供的InvocationHandler接口,實現該接口重寫其調用方法invoke。
public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } ServiceMethod serviceMethod = loadServiceMethod(method); OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } }); }
Proxy.newProxyInstance返回接口的動態代理類,InvocationHandler的invoke方法處理method分為三種情況:1)Object的方法,直接返回Object的實現;2)判斷是否Java8支持的DefaultMethod;3)創建OkHttpCall,通過ServiceMethod轉換為接口的動態代理類。
使用Retrofit的客戶端通過create方法獲取自定義HTTP請求的動態代理類,是客戶端代碼中最重要的部分之一。這里有三個重要組件:
- ServiceMethod
- OKHttpCall
- ServiceMethod.callAdapter
ServiceMethod用於處理Api Service上定義的注解,參數等,得到這個ServiceMethod之后,傳給OkHttpCall,這個OkHttpCall就是對Okhttp的網絡請求封裝的一個類。
ServiceMethod loadServiceMethod(Method method) { ServiceMethod result; synchronized (serviceMethodCache) { result = serviceMethodCache.get(method); if (result == null) { result = new ServiceMethod.Builder(this, method).build(); serviceMethodCache.put(method, result); } } return result; }
關於同步,這里有兩點值得學習:
- 采用synchronized將鎖加在serviceMethodCache上,而不是加到方法上。(直接synchronized加到方法可能會引起Dos,當然,你可以說客戶端不用考慮這種問題);
- serviceMethodCache是用於緩存HTTP請求方法的,初始方法采用LinkedHashMap而不是普通的HashMap。
private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();LinkedHashMap中有一個字段accessOrder,表示是否按照訪問順序進行重排序。默認為false,表示按插入時間順序排序,如果設置為true,則進行重排序。最近最少訪問的元素會被放到隊尾,最先刪除,而最常訪問的元素,則放到隊頭,最后刪除。
把ServiceMethod傳給OkHttpCall實際上就是把網絡接口所需要的URL,參數等條件傳給了OkHttpCall,就可以進行網絡請求了。ServiceMethod中如果沒有配置CallAdapter,則使用默認的DefaultCallAdapterFactory, 得到的結果是Call<?>。
回到正題,你可能已經發現,create方法采用的代理模式和正常的代理模式並不一樣,正常的代理模式只是對真實對象的一層控制,這個真實對象是實現對應的接口的,而這里並沒有真實的對象,它把方法調用最終全部轉發到OKHttp了。
四、簡單工廠模式
本文后面的部分將集中在簡單工廠模式、工廠模式和抽象工廠模式,它們都屬於創建型模式,其主要功能都是將對象的實例化部分抽取出來。簡單工廠模式也稱為靜態工廠模式,包含三種角色:
- Factory工廠角色——負責實現創建所有實例的內部邏輯;
- Product抽象產品角色——創建的所有對象的父類,負責描述所有實例所共有的公共接口;
- ConcreteProduct具體產品角色——繼承自Product,負責具體產品的創建。
簡單工廠模式是一種很常見、很簡單的設計模式,以Platform類為例,其首先包含靜態域PLATFORM,並通過靜態返回供客戶端調用。
private static final Platform PLATFORM = findPlatform(); static Platform get() { return PLATFORM; }
findPlatform其實就是一個靜態工廠方法,根據Class.forName是否拋出ClassNotFoundException來判斷不同的平台。
private static Platform findPlatform() { try { Class.forName("android.os.Build"); if (Build.VERSION.SDK_INT != 0) { return new Android(); } } catch (ClassNotFoundException ignored) { } try { Class.forName("java.util.Optional"); return new Java8(); } catch (ClassNotFoundException ignored) { } try { Class.forName("org.robovm.apple.foundation.NSObject"); return new IOS(); } catch (ClassNotFoundException ignored) { } return new Platform(); }
而Android、Java8、IOS相當於ConcreteProduct的角色,繼承自抽象產品類Platform。
Java8:
static class Java8 extends Platform {}
Android:
static class Android extends Platform {}
IOS:
static class IOS extends Platform {}
五、工廠模式
上述簡單工廠模式中的工廠方法類只有一個Factory,仍以Platform為例,如果在增加一種平台:Windows Phone,那么就需要修改findPlatform方法,添加Windows Phone類的創建。
這種修改模式不符合“開閉原則”,即對擴展開放,對修改封閉。本着可擴展的原則,抽象Factory類的公共部分為抽象類,然后不同的平台工廠繼承自抽象的Factory。需要不用的平台就調用不同的工廠方法,這就是工廠模式。即對簡單工廠中的工廠類進行抽象:
- Factory抽象工廠類——負責工廠類的公共部分;
- ConcreteFactory具體工廠類——繼承自Factory,實現不同特性的工廠。
如果按照工廠模式,通過PlatformFactory類抽象工廠方法,那么大概會是這樣:
public abstract class PlatformFactory { abstract Platform findPlatform(); }
Android、Java8、IOS或者可能新增的Windows Phone工廠繼承自PlatformFactory。
public class AndroidFactory extends PlatformFactory { @Override Platform findPlatform() { try { Class.forName("android.os.Build"); if (Build.VERSION.SDK_INT != 0) { return new Android(); } } catch (ClassNotFoundException ignored) { } return new Platform(); } }
客戶端需要不同的平台對象就調用不同的工廠,但客戶端如果調用錯誤,比如在Android上調用了IOS的工廠,那么就會得到一個Platform的實例,這並不符合要求,這種寫法增加了客戶端的難度,同時,需要引入抽象層,增加多個具體工廠類,維護成本也更大。
所以,在使用工廠設計模式時,一定需要衡量利弊,在特定的場景選擇最合適的設計模式。那么,Retrofit中使用工廠模式的經典例子又是什么呢?CallAdapter!
public interface CallAdapter<T> { Type responseType(); <R> T adapt(Call<R> call); abstract class Factory { public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit); protected static Type getParameterUpperBound(int index, ParameterizedType type) { return Utils.getParameterUpperBound(index, type); } protected static Class<?> getRawType(Type type) { return Utils.getRawType(type); } }
}
CallAdapter是什么呢?見名知義,對Call進行適配,這里涉及到適配器模式,下節會着重說明。這里關注CallAdapter.Factory,CallAdapter.Factory對應上述角色中的Factory抽象工廠類,包含兩個靜態工具方法getParameterUpperBound、getRawType和抽象方法get。
get方法返回不同類型的CallAdapter,RxJavaCallAdapterFactory返回CallAdapter<Observable<?>>,DefaultCallAdapterFactory返回CallAdapter<Call<?>>。
public final class RxJavaCallAdapterFactory extends CallAdapter.Factory { // 省略代碼 @Override public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { // 省略代碼 CallAdapter<Observable<?>> callAdapter = getCallAdapter(returnType, scheduler); // 省略代碼 return callAdapter; } private CallAdapter<Observable<?>> getCallAdapter(Type returnType, Scheduler scheduler) { // 省略代碼 } }
如果需要增加新的CallAdapter,繼承自CallAdapter.Factory,覆蓋get方法即可。符合面向對象軟件設計的“開閉原則”。
六、抽象工廠模式
在配置Retrofit時,除了上述提到的CallAdapter,還需要addConverterFactory。Retrofit調用Okhttp時,將請求內容由T轉換為okhttp3.RequestBody,將返回內容由okhttp3.ResponseBody轉換為T,Converter是就是負責轉換的類。Retrofit官方文檔中給出了多種不同實現的轉換器類,如下:
Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Simple XML: com.squareup.retrofit2:converter-simplexml
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
其中包含JSON轉換的Gson、Jackson,負責PB解析的Protobuf,負責XML解析的Simple XML,這些類具有相同點,均繼承自Converter.Factory。
public interface Converter<F, T> { T convert(F value) throws IOException; abstract class Factory { public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
Retrofit retrofit) { return null; } public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { return null; } public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) { return null; } } }
這里注意下Converter.Factory與CallAdapter.Factory的區別,CallAdapter.Factory只有一個抽象的get方法返回CallAdapter<?>,Converter.Factory有三個方法,分別返回Converter<ResponseBody, ?>、Converter<?, RequestBody>和Converter<?, String>。
相比於工廠模式,具體工廠負責生產具體的產品,每一個具體工廠對應一種具體產品,工廠方法也具有唯一性,一般情況下,一個具體工廠中只有一個工廠方法或者一組重載的工廠方法。但是有時候我們需要一個工廠可以提供多個產品對象,而不是單一的產品對象,例如:上述Converter.Factory需要同時提供請求內容和返回內容的轉換類,這時,就需要考慮抽象工廠模式。抽象工廠模式同樣包含四種角色:
- AbstractFactory:抽象工廠
- ConcreteFactory:具體工廠
- AbstractProduct:抽象產品
- Product:具體產品
標准抽象工廠模式的UML圖如下:(圖片來自互聯網)
這里以GsonConverterFactory為例進行說明。GsonConverterFactory對應ConcreteFactory具體工廠,表示Gson轉換類的工廠,GsonConverterFactory繼承自AbstractFactory抽象工廠——Converter.Factory,重寫了requestBodyConverter方法和responseBodyConverter方法,相當於上圖中的createProductA和createProductB。
public final class GsonConverterFactory extends Converter.Factory {
// 省略代碼 @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new GsonResponseBodyConverter<>(gson, adapter); } @Override public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type)); return new GsonRequestBodyConverter<>(gson, adapter); } }
這里GsonRequestBodyConverter對應ProductA,GsonResponseBodyConverter對應ProductB。
final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> { // 省略代碼 }
final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> { // 省略代碼 }
Converter<T, RequestBody>對應抽象產品AbstractProductA,Converter<ResponseBody, T>對應抽象產品AbstractProductB。
最后思考下:為什么Converter.Factory需要用抽象工廠模式,用工廠模式可以嗎?如果用工廠模式,客戶端需要怎么配置?