自己动手,实现一款轻量级 HTTP 调用工具
今日推薦
吊打 ThreadLocal,談?wù)凢astThreadLocal為啥能這么快?一個(gè)Github項(xiàng)目搞定微信、QQ、支付寶等第三方登錄注解+反射優(yōu)雅的實(shí)現(xiàn)Excel導(dǎo)入導(dǎo)出(通用版)Fluent Mybatis 牛逼!Nginx 常用配置清單這玩意比ThreadLocal叼多了,嚇得我趕緊分享出來。來源:juejin.cn/post/6854573219899244551
本篇文章繼續(xù)繼續(xù)介紹retrofit-spring-boot-starter的實(shí)現(xiàn)原理,從零開始介紹如何在spring-boot項(xiàng)目中基于Retrofit實(shí)現(xiàn)自己的輕量級http調(diào)用工具。
項(xiàng)目源碼:
https://github.com/LianjiaTech/retrofit-spring-boot-starter
確定實(shí)現(xiàn)思路
我們首先直接看一下使用retrofit原始API是如何發(fā)起一個(gè)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();//?實(shí)際業(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請求。但是如果我們項(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)知識介紹
為了方便后面的介紹,我們先得了解一下幾個(gè)相關(guān)知識點(diǎn)。
spring容器初始化
我們首先要簡單了解一下spring容器初始化。簡單來講,spring容器初始化主要包含以下2個(gè)步驟:
注冊Bean定義:掃描并解析配置文件或者某些注解得到Bean屬性(包括beanName、beanClassName、scope、isSingleton等等),然后基于這個(gè)bean屬性創(chuàng)建BeanDefinition對象,最后將其注冊到BeanDefinitionRegistry中。
創(chuàng)建Bean實(shí)例:根據(jù)BeanDefinitionRegistry里面的BeanDefinition信息,創(chuàng)建Bean實(shí)例,并將實(shí)例對象保存到spring容器中,創(chuàng)建的方式包括反射創(chuàng)建、工廠方法創(chuàng)建和工廠Bean(FactoryBean)創(chuàng)建等等。
當(dāng)然,實(shí)際的spring容器初始化比這復(fù)雜的多,考慮到這塊不是本文的重點(diǎn),暫時(shí)這么理解就行。推薦:Java進(jìn)階視頻資源
Retrofit對象簡介
我們已經(jīng)知道使用Retrofit對象可以創(chuàng)建接口代理對象,接下來看一下Retrofit的UML類圖(只列出了我們關(guān)注的依賴):
通過分析UML類圖,我們可以發(fā)現(xiàn),構(gòu)建Retrofit對象的時(shí)候,可以注入以下4個(gè)屬性:
HttpUrl:http請求的baseUrl。
CallAdapter:將Call<T>適配為接口方法返回值類型。
Converter:將@Body標(biāo)記的方法參數(shù)序列化為請求體數(shù)據(jù);將響應(yīng)體數(shù)據(jù)反序列化為響應(yīng)對象。
OkHttpClient:底層發(fā)送http請求的客戶端對象。
而構(gòu)建OkHttpClient對象的時(shí)候,可以注入Interceptor(請求攔截器)和ConnectionPool(連接池)屬性。
因此為了構(gòu)建Retrofit對象,我們要先創(chuàng)建HttpUrl、CallAdapter、Converter和OkHttpClient;而要構(gòu)建OkHttpClient對象就得先創(chuàng)建Interceptor和ConnectionPool。
實(shí)現(xiàn)詳解
注冊Bean定義
為了實(shí)現(xiàn)將HttpService接口代理對象完全交由spring容器管理,首先就得將HttpService接口掃描并注冊到BeanDefinitionRegistry中。
spring提供了ImportBeanDefinitionRegistrar接口,支持了自定義注冊BeanDefinition的功能。因此我們先定義RetrofitClientRegistrar類用來實(shí)現(xiàn)上述功能。具體實(shí)現(xiàn)如下:
RetrofitClientRegistrar
RetrofitClientRegistrar從@RetrofitScan注解中提取出要掃描的基礎(chǔ)包路徑之后,將具體的掃描注冊邏輯交給了ClassPathRetrofitClientScanner處理。
public?class?RetrofitClientRegistrar?implements?ImportBeanDefinitionRegistrar,?ResourceLoaderAware,?BeanClassLoaderAware?{//?省略其它代碼@Overridepublic?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();//?掃描并注冊到BeanDefinitionscanner.doScan(basePackages);} }ClassPathRetrofitClientScanner
ClassPathRetrofitClientScanner繼承了ClassPathBeanDefinitionScanner,這是Spring提供的類路徑下BeanDefinition的掃描器。
需要注意的一點(diǎn)是:BeanDefinition的beanClass屬性全部設(shè)置為了RetrofitFactoryBean.class,同時(shí)將接口自身的類型傳遞到了RetrofitFactoryBean的retrofitInterface屬性中。這說明,最終創(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è)置為RetrofitFactoryBeandefinition.setBeanClass(RetrofitFactoryBean.class);}} }這樣,我們就完成了掃描指定路徑下帶有@RetrofitClient注解的接口,并將其注冊到BeanDefinitionRegistry的功能了。推薦:Java進(jìn)階視頻資源
@RetrofitClient注解要標(biāo)識在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對象能夠生成接口代理對象。因此getObject()方法的核心就是構(gòu)建Retrofit對象,并基于此生成http接口代理對象。
配置項(xiàng)和@RetrofitClient為了更加靈活的構(gòu)建Retrofit對象,我們可以通過配置項(xiàng)以及@RetrofitClient注解屬性傳遞一些動(dòng)態(tài)參數(shù)信息。@RetrofitClient包含的屬性如下:
baseUrl:用來創(chuàng)建Retrofit的HttpUrl,表示該接口下所有請求都適用的基礎(chǔ)url。
poolName:該接口下請求使用的連接池的名稱,決定了ConnectionPool對象的取值。
connectTimeoutMs/readTimeoutMs/writeTimeoutMs:用于構(gòu)建OkHttpClient對象的超時(shí)時(shí)間設(shè)置。
logLevel/logStrategy:配置該接口下請求的日志打印級別和日志打印策略,可用來創(chuàng)建日志打印攔截器Interceptor。
RetrofitFactoryBean``RetrofitFactoryBean實(shí)現(xiàn)邏輯非常復(fù)雜,概括起來主要包含以下幾點(diǎn):
通過配置項(xiàng)數(shù)據(jù)以及@RetrofitClient注解數(shù)據(jù)完成了Retrofit對象的構(gòu)建。
每一個(gè)HttpService接口就會(huì)構(gòu)建一個(gè)Retrofit對象,每一個(gè)Retrofit對象就會(huì)構(gòu)建對應(yīng)的OkHttpClient對象。
可擴(kuò)展的注解式攔截器是通過InterceptMark注解標(biāo)記實(shí)現(xiàn)的,路徑攔截匹配是通過BasePathMatchInterceptor實(shí)現(xiàn)的。
這樣,我們就完成了創(chuàng)建HttpServiceBean實(shí)例的功能了。在使用的時(shí)候直接注入HttpService,然后調(diào)用其方法就能發(fā)送對應(yīng)的http請求。
結(jié)語
總的來說,在spring-boot項(xiàng)目中基于Retrofit實(shí)現(xiàn)自己的輕量級http調(diào)用工具的核心只有兩點(diǎn):第一是注冊HttpService接口的BeanDefinition,第二就是構(gòu)建Retrofit來創(chuàng)建HttpService的代理對象。
如需了解更多細(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),請聯(lián)系刪除! 下面是部分截圖:獲取方式點(diǎn)擊下方公眾號,回復(fù):好好學(xué)Java,即可獲取。總結(jié)
以上是生活随笔為你收集整理的自己动手,实现一款轻量级 HTTP 调用工具的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一个Github项目搞定微信、QQ、支付
- 下一篇: MySQL最高每秒57万写入,带你装X,