手把手教你基於Retrofit實現本身的輕量級http調用工具

在《spring-boot項目最優雅的http客戶端工具,用它就夠了,太香了!》這篇文章中,咱們知道了retrofit-spring-boot-starter的使用方式。本篇文章繼續繼續介紹retrofit-spring-boot-starter的實現原理,從零開始介紹如何在spring-boot項目中基於Retrofit實現本身的輕量級http調用工具java

項目源碼:retrofit-spring-boot-startergit

肯定實現思路

咱們首先直接看一下使用retrofit原始API是如何發起一個http請求的。github

  1. 定義接口spring

    public interface GitHubService {
    @GET("users/{user}/repos")
    Call<List<Repo>> listRepos(@Path("user") String user);
    }
    複製代碼
  2. 建立接口代理對象sql

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.github.com/")
        .build();
    
    // 實際業務場景構建Retrofit比這複雜多了,這裏最簡單化處理
    
    GitHubService service = retrofit.create(GitHubService.class);
    複製代碼
  3. 發起請求api

    Call<List<Repo>> repos = service.listRepos("octocat");
    複製代碼

能夠看到,Retrofit自己已經很好的支持了經過接口發起htp請求。可是若是咱們項目每個業務代碼都要寫上面的樣板代碼,會很是的繁瑣。有沒有一種方式讓用戶只關注接口定義,其它事情所有交給框架自動處理?這個時候咱們可能會聯想到spring-boot項目下使用Mybatis,用戶只須要定義Mapper接口和書寫sql便可,徹底不用管與JDBC的交互細節。與之相似,咱們最終也要實現讓用戶只須要定義HttpService接口,不用管其餘底層實現細節markdown

相關知識介紹

爲了方便後面的介紹,咱們先得了解一下幾個相關知識點。app

spring容器初始化

咱們首先要簡單瞭解一下spring容器初始化。簡單來說,spring容器初始化主要包含如下2個步驟:框架

  1. 註冊Bean定義:掃描並解析配置文件或者某些註解獲得Bean屬性(包括beanNamebeanClassNamescopeisSingleton等等),而後基於這個bean屬性建立BeanDefinition對象,最後將其註冊到BeanDefinitionRegistry中。
  2. 建立Bean實例:根據BeanDefinitionRegistry裏面的BeanDefinition信息,建立Bean實例,並將實例對象保存到spring容器中,建立的方式包括反射建立、工廠方法建立和工廠Bean(FactoryBean)建立等等。

固然,實際的spring容器初始化比這複雜的多,考慮到這塊不是本文的重點,暫時這麼理解就行。ide

Retrofit對象簡介

咱們已經知道使用Retrofit對象能夠建立接口代理對象,接下來看一下Retrofit的UML類圖(只列出了咱們關注的依賴): 經過分析UML類圖,咱們能夠發現,構建Retrofit對象的時候,能夠注入如下4個屬性:

  1. HttpUrlhttp請求的baseUrl
  2. CallAdapter:將Call<T>適配爲接口方法返回值類型。
  3. Converter:將@Body標記的方法參數序列化爲請求體數據;將響應體數據反序列化爲響應對象。
  4. OkHttpClient:底層發送http請求的客戶端對象。

而構建OkHttpClient對象的時候,能夠注入Interceptor(請求攔截器)和ConnectionPool(鏈接池)屬性。

所以爲了構建Retrofit對象,咱們要先建立HttpUrlCallAdapterConverterOkHttpClient;而要構建OkHttpClient對象就得先建立InterceptorConnectionPool

實現詳解

註冊Bean定義

爲了實現將HttpService接口代理對象徹底交由spring容器管理,首先就得將HttpService接口掃描並註冊到BeanDefinitionRegistry中。spring提供了ImportBeanDefinitionRegistrar接口,支持了自定義註冊BeanDefinition的功能。所以咱們先定義RetrofitClientRegistrar類用來實現上述功能。具體實現以下:

  1. RetrofitClientRegistrar

    RetrofitClientRegistrar@RetrofitScan註解中提取出要掃描的基礎包路徑以後,將具體的掃描註冊邏輯交給了ClassPathRetrofitClientScanner處理。

    public class RetrofitClientRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware {
    
        // 省略其它代碼
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AnnotationAttributes attributes = AnnotationAttributes
                    .fromMap(metadata.getAnnotationAttributes(RetrofitScan.class.getName()));
            // 掃描指定路徑下@RetrofitClient註解的接口,並註冊到BeanDefinitionRegistry
            // 真正的掃描註冊邏輯交給了ClassPathRetrofitClientScanner執行
            ClassPathRetrofitClientScanner scanner = new ClassPathRetrofitClientScanner(registry, classLoader);
            if (resourceLoader != null) {
                scanner.setResourceLoader(resourceLoader);
            }
            //指定掃描的基礎包
            String[] basePackages = getPackagesToScan(attributes);
            scanner.registerFilters();
            // 掃描並註冊到BeanDefinition
            scanner.doScan(basePackages);
        }
    }
    複製代碼
  2. ClassPathRetrofitClientScanner

    ClassPathRetrofitClientScanner繼承了ClassPathBeanDefinitionScanner,這是Spring提供的類路徑下BeanDefinition的掃描器。須要注意的一點是:BeanDefinitionbeanClass屬性所有設置爲了RetrofitFactoryBean.class,同時將接口自身的類型傳遞到了RetrofitFactoryBeanretrofitInterface屬性中。這說明,最終建立Bean實例是經過RetrofitFactoryBean來完成的。

    public class ClassPathRetrofitClientScanner extends ClassPathBeanDefinitionScanner {
    
        // 省略其它代碼
    
        private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
            GenericBeanDefinition definition;
            for (BeanDefinitionHolder holder : beanDefinitions) {
                definition = (GenericBeanDefinition) holder.getBeanDefinition();
                if (logger.isDebugEnabled()) {
                    logger.debug("Creating RetrofitClientBean with name '" + holder.getBeanName()
                            + "' and '" + definition.getBeanClassName() + "' Interface");
                }
                definition.getConstructorArgumentValues().addGenericArgumentValue(Objects.requireNonNull(definition.getBeanClassName()));
                // beanClass所有設置爲RetrofitFactoryBean
                definition.setBeanClass(RetrofitFactoryBean.class);
            }
        }
    }
    複製代碼

這樣,咱們就完成了掃描指定路徑下帶有@RetrofitClient註解的接口,並將其註冊到BeanDefinitionRegistry的功能了

@RetrofitClient註解要標識在HttpService的接口上!@RetrofitScan指定了要掃描的包路徑。具體可看考源碼。

建立Bean實例

上面已經說了建立Bean實例其實是經過RetrofitFactoryBean實現的。具體就是實現FactoryBean<T>接口,而後重寫getObject()方法來完成建立接口Bean實例的邏輯。而且,咱們也已經知道經過Retrofit對象可以生成接口代理對象。所以getObject()方法的核心就是構建Retrofit對象,並基於今生成http接口代理對象。

  1. 配置項和@RetrofitClient 爲了更加靈活的構建Retrofit對象,咱們能夠經過配置項以及@RetrofitClient註解屬性傳遞一些動態參數信息。@RetrofitClient包含的屬性以下:

    1. baseUrl:用來建立RetrofitHttpUrl,表示該接口下全部請求都適用的基礎url
    2. poolName:該接口下請求使用的鏈接池的名稱,決定了ConnectionPool對象的取值。
    3. connectTimeoutMs/readTimeoutMs/writeTimeoutMs:用於構建OkHttpClient對象的超時時間設置。
    4. logLevel/logStrategy:配置該接口下請求的日誌打印級別和日誌打印策略,可用來建立日誌打印攔截器Interceptor
  2. RetrofitFactoryBean RetrofitFactoryBean實現邏輯很是複雜,歸納起來主要包含如下幾點:

    1. 經過配置項數據以及@RetrofitClient註解數據完成了Retrofit對象的構建。
    2. 每個HttpService接口就會構建一個Retrofit對象,每個Retrofit對象就會構建對應的OkHttpClient對象。
    3. 可擴展的註解式攔截器是經過InterceptMark註解標記實現的,路徑攔截匹配是經過BasePathMatchInterceptor實現的。
    public class RetrofitFactoryBean<T> implements FactoryBean<T>, EnvironmentAware, ApplicationContextAware {
    
        // 省略其它代碼
    
        public RetrofitFactoryBean(Class<T> retrofitInterface) {
            this.retrofitInterface = retrofitInterface;
        }
    
        @Override
        @SuppressWarnings("unchecked")
        public T getObject() throws Exception {
            // 接口校驗
            checkRetrofitInterface(retrofitInterface);
            // 構建Retrofit對象
            Retrofit retrofit = getRetrofit(retrofitInterface);
            // 基於Retrofit建立接口代理對象
            return retrofit.create(retrofitInterface);
        }
    
        /** * 獲取OkHttpClient實例,一個接口接口對應一個OkHttpClient * * @param retrofitClientInterfaceClass retrofitClient接口類 * @return OkHttpClient實例 */
        private synchronized OkHttpClient getOkHttpClient(Class<?> retrofitClientInterfaceClass) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
            // 基於各類條件構建OkHttpClient
        }
    
        /** * 獲取Retrofit實例,一個retrofitClient接口對應一個Retrofit實例 * * @param retrofitClientInterfaceClass retrofitClient接口類 * @return Retrofit實例 */
        private synchronized Retrofit getRetrofit(Class<?> retrofitClientInterfaceClass) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
            // 構建retrofit
        }
    複製代碼

這樣,咱們就完成了建立HttpServiceBean實例的功能了。在使用的時候直接注入HttpService,而後調用其方法就能發送對應的http請求。

結語

總的來講,在spring-boot項目中基於Retrofit實現本身的輕量級http調用工具的核心只有兩點:第一是註冊HttpService接口的BeanDefinition,第二就是構建Retrofit來建立HttpService的代理對象。如需瞭解更多細節,建議直接查看retrofit-spring-boot-starter源碼

原創不易,以爲文章寫得不錯的小夥伴,點個贊👍 鼓勵一下吧~

歡迎關注個人開源項目:一款適用於SpringBoot的輕量級HTTP調用框架

相關文章
相關標籤/搜索