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

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

          共 10094字,需瀏覽 21分鐘

           ·

          2021-09-12 16:02


          點(diǎn)擊上方“程序員大白”,選擇“星標(biāo)”公眾號(hào)

          重磅干貨,第一時(shí)間送達(dá)

          來源:juejin.cn/post/6854573219899244551

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

          項(xiàng)目源碼:

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

          確定實(shí)現(xiàn)思路

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

          1.定義接口

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

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

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

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

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

          3.發(fā)起請(qǐng)求

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

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

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

          相關(guān)知識(shí)介紹

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

          spring容器初始化

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

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

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

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

          Retrofit對(duì)象簡(jiǎn)介

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

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

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

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

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

          實(shí)現(xiàn)詳解

          注冊(cè)Bean定義

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

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

          1. RetrofitClientRegistrar

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

          public class RetrofitClientRegistrar implements ImportBeanDefinitionRegistrarResourceLoaderAwareBeanClassLoaderAware {

              // 省略其它代碼

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

          ClassPathRetrofitClientScanner

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

          需要注意的一點(diǎn)是:BeanDefinitionbeanClass屬性全部設(shè)置為了RetrofitFactoryBean.class,同時(shí)將接口自身的類型傳遞到了RetrofitFactoryBeanretrofitInterface屬性中。這說明,最終創(chuàng)建Bean實(shí)例是通過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注解的接口,并將其注冊(cè)到BeanDefinitionRegistry的功能了。推薦:Java進(jìn)階視頻資源

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

          創(chuàng)建Bean實(shí)例

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

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

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

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

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

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

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

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

          結(jié)語

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

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

          歡迎一鍵三連

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


          “拍一拍” 能撤回了 !?。?/a>

          5款Chrome插件,第1款絕對(duì)良心!

          為開發(fā)色情游戲,這家公司赴日尋找AV女優(yōu)拍攝,期望暴力賺錢結(jié)果...

          拼多多終于釀成慘劇

          華為阿里下班時(shí)間曝光:所有的光鮮,都有加班的味道


          關(guān)


          ,學(xué),西學(xué)學(xué)運(yùn)護(hù)號(hào),質(zhì),結(jié)識(shí),關(guān)[],學(xué)習(xí)進(jìn)!


          瀏覽 63
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  成年片黄色片网站视频 | 大屌在线 | 黄色在线免费在线免费 | 免费一级黄色电影 | 亚洲大逼色 |