Retrofit源碼設計模式解析(上)


Retrofit通過注解的方法標記HTTP請求參數,支持常用HTTP方法,統一返回值解析,支持異步/同步的請求方式,將HTTP請求對象化,參數化。真正執行網絡訪問的是Okhttp,Okhttp支持HTTP&HTTP2,因此,使用Retrofit可以支持REST、HTTPS及SPDY。

行業內分析Retrofit的使用方法的文章已經比較豐富,這里不再贅述,如想了解這部分內容,請參考如下鏈接。

用 Retrofit 2 簡化 HTTP 請求

Retrofit 源碼解析

本文主要從設計模式的角度分享對Retrofit源碼的一些理解。

  1. 外觀模式
  2. 建造者模式
  3. 代理模式
  4. 簡單工廠模式
  5. 工廠模式
  6. 抽象工廠模式

一、外觀模式

在封裝某些特定功能的子系統時,外觀模式是一種很好的設計規范。即該子系統的外部與內部通信時通過一個統一的對象進行。Retrofit是整個庫的一個入口類,Retrofit庫的使用基本都是圍繞着這個類。外觀模式具有高內聚、低耦合的特性,對外提供簡單統一的接口,隱蔽了子系統具體的實現、隔離變化。

Retrofit的外觀模式的UML類圖如下所示。

image

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

關於同步,這里有兩點值得學習:

  1. 采用synchronized將鎖加在serviceMethodCache上,而不是加到方法上。(直接synchronized加到方法可能會引起Dos,當然,你可以說客戶端不用考慮這種問題);
  2. 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圖如下:(圖片來自互聯網)

AbatractFactory

這里以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需要用抽象工廠模式,用工廠模式可以嗎?如果用工廠模式,客戶端需要怎么配置?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM