【賞碼會】HTTP Client中的瑞士軍刀:Retrofit

寫在前面

最近開始在GitHub上找一些優秀的開源項目,跟團隊一塊兒閱讀源代碼,每週一次,每次一個半小時左右,美其名曰「賞碼會」(還記得《唐伯虎點秋香》那句「賞花賞月賞秋香」嗎?)。爲何要閱讀源代碼?好處舉不勝舉,好比學習如何合理的命名,如何寫出簡潔、清晰的註釋,如何編寫有效的單元測試,知道良好的編碼風格是什麼樣的。有一些積累以後,能夠試試看找一找隱藏在代碼裏的設計模式,加一些新的單元測試,想想若是本身實現會如何設計。閱讀源代碼能夠說是有百利而無一害,屬於典型的第二象限的事(參見《高效能人士的七個習慣》)。html

Retrofit簡介

第一次「賞碼會」我選的是Retrofit項目,爲啥選它呢?第一,小巧(核心代碼不到5000行),第二,高Star(17+K),第三,平時一直在用。先簡單介紹一下Retrofit這個框架。Retrofit是Square公司開源的一個Java實現的輕量級HTTP Client框架,本質上是對Square公司另外一個開源框架OkHTTP的一層type-safe的封裝。所謂的type-safe,個人理解就是將OkHTTP原生的Request/Response對象經過類型安全的方式轉化爲其餘任意類型的對象,好比String,用戶自定義類型等。java

面向接口的聲明式API定義風格是Retrofit最受歡迎的特性,例以下面的GitHubService接口的listRepos方法定義了GitHub的List user repositories API。git

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

無需定義具體的實現類,就能夠直接調用,例如:程序員

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();
GitHubService service = retrofit.create(GitHubService.class);
Call<List<Repo>> repos = service.listRepos("octocat");

有經驗的Java程序員馬上就能看出,相對於其餘的HTTP Client框架,好比Apache HttpClient或者Async Http Client,使用Retrofit編程效率將產生質的提高。github

核心類

從GitHub拉取Retrofit的源代碼,導入retrofit子工程,核心代碼都在retrofit包下。apache

核心類列舉以下:編程

  • Retrofit: Retrofit框架的門面類,大多數狀況下,你的代碼中只須要用到它。設計模式

  • ServiceMethod: 對應接口類中的一個方法(好比上文中的listRepos),負責解析方法簽名中用到的各類註解,生成最終的Request對象。api

  • Call: 相似於Java 8裏面的CompletableFuture,提供異步支持。安全

  • CallAdapter: Retrofit默認只接受Call<?>做爲方法返回類型,若是須要使用其餘類型,就要添加額外的CallAdapter。

  • Converter: Retrofit默認只接受Response和Void做爲Call<?>的類型參數,若是須要使用其餘類型,就要添加額外的Converter。

解謎

和任何形式的閱讀(讀書,讀人,讀心)同樣,要讀懂源代碼,必定要帶着問題去讀。爲了幫助理解上述幾個核心類的關係,簡單列舉幾個我閱讀代碼時思考的問題,

謎1:爲啥只有接口?實現類在哪?

答案很簡單,由於使用了JDK的動態代理,很是討巧的設計。

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<Object, Object> serviceMethod =
                (ServiceMethod<Object, Object>) loadServiceMethod(method);
            OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
          }
        });
  }

謎2:我不想用Call,怎樣才能使用其餘返回類型?

剛纔已經提到了,經過Retrofit.Builder#addCallAdapterFactory()添加相應的CallAdapter,例如想返回CompleteableFuture,可使用Retrofit提供的Java8CallAdapterFactory

謎3:如何打印請求和響應日誌?

跟動態設置Headers相似,能夠自定義用於打印日誌的OkHttp interceptor,而後添加到本身建立的OkHttpClient實例,再綁定Retrofit.Builder#client()。

漫談

總的來講,Retrofit框架設計精巧,上手簡單,開發效率高,但也存在一些不足。第一,跟OkHTTP框架綁定太死,不像Feign那麼靈活,支持多種Client。第二,和JDK的動態代理強綁定,對其餘AOP方式不友好,好比這個Issue提到的Hystrix集成問題。

時間有限,先寫到這裏,將來我會不按期放一些「賞碼會」的心得,歡迎到個人GitHub留言交流。

傳送門

相關文章
相關標籤/搜索