<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          自己動手,實現(xiàn)一款輕量級 HTTP 調(diào)用工具

          共 10052字,需瀏覽 21分鐘

           ·

          2021-09-15 10:41

          今日推薦
          吊打 ThreadLocal,談?wù)凢astThreadLocal為啥能這么快?
          一個Github項目搞定微信、QQ、支付寶等第三方登錄
          注解+反射優(yōu)雅的實現(xiàn)Excel導(dǎo)入導(dǎo)出(通用版)
          Fluent Mybatis 牛逼!
          Nginx 常用配置清單
          這玩意比ThreadLocal叼多了,嚇得我趕緊分享出來。

          來源:juejin.cn/post/6854573219899244551

          本篇文章繼續(xù)繼續(xù)介紹retrofit-spring-boot-starter的實現(xiàn)原理,從零開始介紹如何在spring-boot項目中基于Retrofit實現(xiàn)自己的輕量級http調(diào)用工具。

          項目源碼:

          https://github.com/LianjiaTech/retrofit-spring-boot-starter

          確定實現(xiàn)思路

          我們首先直接看一下使用retrofit原始API是如何發(fā)起一個http請求的。

          1.定義接口

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

          2.創(chuàng)建接口代理對象

          Retrofit retrofit = new Retrofit.Builder()
              .baseUrl("https://api.github.com/")
              .build();

          // 實際業(yè)務(wù)場景構(gòu)建Retrofit比這復(fù)雜多了,這里最簡單化處理

          GitHubService service = retrofit.create(GitHubService.class);

          3.發(fā)起請求

          Call<List<Repo>> repos = service.listRepos("octocat");

          可以看到,Retrofit本身已經(jīng)很好的支持了通過接口發(fā)起htp請求。但是如果我們項目每一個業(yè)務(wù)代碼都要寫上面的樣板代碼,會非常的繁瑣。有沒有一種方式讓用戶只關(guān)注接口定義,其它事情全部交給框架自動處理?

          這個時候我們可能會聯(lián)想到spring-boot項目下使用Mybatis,用戶只需要定義Mapper接口和書寫sql即可,完全不用管與JDBC的交互細節(jié)。與之類似,我們最終也要實現(xiàn)讓用戶只需要定義HttpService接口,不用管其他底層實現(xiàn)細節(jié)。

          相關(guān)知識介紹

          為了方便后面的介紹,我們先得了解一下幾個相關(guān)知識點。

          spring容器初始化

          我們首先要簡單了解一下spring容器初始化。簡單來講,spring容器初始化主要包含以下2個步驟:

          1. 注冊Bean定義:掃描并解析配置文件或者某些注解得到Bean屬性(包括beanName、beanClassName、scope、isSingleton等等),然后基于這個bean屬性創(chuàng)建BeanDefinition對象,最后將其注冊到BeanDefinitionRegistry中。

          2. 創(chuàng)建Bean實例:根據(jù)BeanDefinitionRegistry里面的BeanDefinition信息,創(chuàng)建Bean實例,并將實例對象保存到spring容器中,創(chuàng)建的方式包括反射創(chuàng)建、工廠方法創(chuàng)建和工廠Bean(FactoryBean)創(chuàng)建等等。

          當然,實際的spring容器初始化比這復(fù)雜的多,考慮到這塊不是本文的重點,暫時這么理解就行。推薦:Java進階視頻資源

          Retrofit對象簡介

          我們已經(jīng)知道使用Retrofit對象可以創(chuàng)建接口代理對象,接下來看一下Retrofit的UML類圖(只列出了我們關(guān)注的依賴):

          通過分析UML類圖,我們可以發(fā)現(xiàn),構(gòu)建Retrofit對象的時候,可以注入以下4個屬性:

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

          而構(gòu)建OkHttpClient對象的時候,可以注入Interceptor(請求攔截器)和ConnectionPool(連接池)屬性。

          因此為了構(gòu)建Retrofit對象,我們要先創(chuàng)建HttpUrlCallAdapter、ConverterOkHttpClient;而要構(gòu)建OkHttpClient對象就得先創(chuàng)建InterceptorConnectionPool

          實現(xiàn)詳解

          注冊Bean定義

          為了實現(xiàn)將HttpService接口代理對象完全交由spring容器管理,首先就得將HttpService接口掃描并注冊到BeanDefinitionRegistry中。

          spring提供了ImportBeanDefinitionRegistrar接口,支持了自定義注冊BeanDefinition的功能。因此我們先定義RetrofitClientRegistrar類用來實現(xiàn)上述功能。具體實現(xiàn)如下:

          1. RetrofitClientRegistrar

          RetrofitClientRegistrar@RetrofitScan注解中提取出要掃描的基礎(chǔ)包路徑之后,將具體的掃描注冊邏輯交給了ClassPathRetrofitClientScanner處理。

          public class RetrofitClientRegistrar implements ImportBeanDefinitionRegistrarResourceLoaderAwareBeanClassLoaderAware {

              // 省略其它代碼

              @Override
              public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
                  AnnotationAttributes attributes = AnnotationAttributes
                          .fromMap(metadata.getAnnotationAttributes(RetrofitScan.class.getName()));
                  // 掃描指定路徑下@RetrofitClient注解的接口,并注冊到BeanDefinitionRegistry
                  // 真正的掃描注冊邏輯交給了ClassPathRetrofitClientScanner執(zhí)行
                  ClassPathRetrofitClientScanner scanner = new ClassPathRetrofitClientScanner(registry, classLoader);
                  if (resourceLoader != null) {
                      scanner.setResourceLoader(resourceLoader);
                  }
                  //指定掃描的基礎(chǔ)包
                  String[] basePackages = getPackagesToScan(attributes);
                  scanner.registerFilters();
                  // 掃描并注冊到BeanDefinition
                  scanner.doScan(basePackages);
              }
          }

          ClassPathRetrofitClientScanner

          ClassPathRetrofitClientScanner繼承了ClassPathBeanDefinitionScanner,這是Spring提供的類路徑下BeanDefinition的掃描器。

          需要注意的一點是:BeanDefinitionbeanClass屬性全部設(shè)置為了RetrofitFactoryBean.class,同時將接口自身的類型傳遞到了RetrofitFactoryBeanretrofitInterface屬性中。這說明,最終創(chuàng)建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全部設(shè)置為RetrofitFactoryBean
                      definition.setBeanClass(RetrofitFactoryBean.class);
                  }
              }
          }

          這樣,我們就完成了掃描指定路徑下帶有@RetrofitClient注解的接口,并將其注冊到BeanDefinitionRegistry的功能了。推薦:Java進階視頻資源

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

          創(chuàng)建Bean實例

          上面已經(jīng)說了創(chuàng)建Bean實例實際上是通過RetrofitFactoryBean實現(xiàn)的。具體就是實現(xiàn)FactoryBean<T>接口,然后重寫getObject()方法來完成創(chuàng)建接口Bean實例的邏輯。

          并且,我們也已經(jīng)知道通過Retrofit對象能夠生成接口代理對象。因此getObject()方法的核心就是構(gòu)建Retrofit對象,并基于此生成http接口代理對象。

          1. 配置項和@RetrofitClient為了更加靈活的構(gòu)建Retrofit對象,我們可以通過配置項以及@RetrofitClient注解屬性傳遞一些動態(tài)參數(shù)信息。@RetrofitClient包含的屬性如下:
            1. baseUrl:用來創(chuàng)建RetrofitHttpUrl,表示該接口下所有請求都適用的基礎(chǔ)url
            2. poolName:該接口下請求使用的連接池的名稱,決定了ConnectionPool對象的取值。
            3. connectTimeoutMs/readTimeoutMs/writeTimeoutMs:用于構(gòu)建OkHttpClient對象的超時時間設(shè)置。
            4. logLevel/logStrategy:配置該接口下請求的日志打印級別和日志打印策略,可用來創(chuàng)建日志打印攔截器Interceptor
          2. RetrofitFactoryBean``RetrofitFactoryBean實現(xiàn)邏輯非常復(fù)雜,概括起來主要包含以下幾點:
            1. 通過配置項數(shù)據(jù)以及@RetrofitClient注解數(shù)據(jù)完成了Retrofit對象的構(gòu)建。
            2. 每一個HttpService接口就會構(gòu)建一個Retrofit對象,每一個Retrofit對象就會構(gòu)建對應(yīng)的OkHttpClient對象。
            3. 可擴展的注解式攔截器是通過InterceptMark注解標記實現(xiàn)的,路徑攔截匹配是通過BasePathMatchInterceptor實現(xiàn)的。
          public class RetrofitFactoryBean<Timplements FactoryBean<T>, EnvironmentAwareApplicationContextAware {

              // 省略其它代碼

              public RetrofitFactoryBean(Class<T> retrofitInterface) {
                  this.retrofitInterface = retrofitInterface;
              }

              @Override
              @SuppressWarnings("unchecked")
              public T getObject() throws Exception {
                  // 接口校驗
                  checkRetrofitInterface(retrofitInterface);
                  // 構(gòu)建Retrofit對象
                  Retrofit retrofit = getRetrofit(retrofitInterface);
                  // 基于Retrofit創(chuàng)建接口代理對象
                  return retrofit.create(retrofitInterface);
              }

              /**
              * 獲取OkHttpClient實例,一個接口接口對應(yīng)一個OkHttpClient
              *
              * @param retrofitClientInterfaceClass retrofitClient接口類
              * @return OkHttpClient實例
              */

              private synchronized OkHttpClient getOkHttpClient(Class<?> retrofitClientInterfaceClass) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
                  // 基于各種條件構(gòu)建OkHttpClient
              }

              /**
              * 獲取Retrofit實例,一個retrofitClient接口對應(yīng)一個Retrofit實例
              *
              * @param retrofitClientInterfaceClass retrofitClient接口類
              * @return Retrofit實例
              */

              private synchronized Retrofit getRetrofit(Class<?> retrofitClientInterfaceClass) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
                  // 構(gòu)建retrofit
              }

          這樣,我們就完成了創(chuàng)建HttpServiceBean實例的功能了。在使用的時候直接注入HttpService,然后調(diào)用其方法就能發(fā)送對應(yīng)的http請求。

          結(jié)語

          總的來說,在spring-boot項目中基于Retrofit實現(xiàn)自己的輕量級http調(diào)用工具的核心只有兩點:第一是注冊HttpService接口的BeanDefinition,第二就是構(gòu)建Retrofit來創(chuàng)建HttpService的代理對象。

          如需了解更多細節(jié),建議直接查看retrofit-spring-boot-starter源碼。

          歡迎一鍵三連

          推薦一些很不錯的計算機學(xué)習(xí)教程,包括:數(shù)據(jù)結(jié)構(gòu)、算法、計算機網(wǎng)絡(luò)、操作系統(tǒng)、Java(spring、springmvc、springboot、springcloud等)等等 ,全部收集于網(wǎng)絡(luò),如果有侵權(quán),請聯(lián)系刪除!
          下面是部分截圖:
          獲取方式

          點擊下方公眾號,回復(fù):好好學(xué)Java,即可獲取。
          瀏覽 37
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美日韩国产在线播放 | 免费看的操逼视频 | 婷婷丁香五月综合 | 再拍偷拍成人视频 | 中国女人真人一级毛片 |