自己動(dòng)手,實(shí)現(xiàn)一款輕量級(jí) HTTP 調(diào)用工具
點(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è)步驟:
注冊(cè)Bean定義:掃描并解析配置文件或者某些注解得到Bean屬性(包括
beanName、beanClassName、scope、isSingleton等等),然后基于這個(gè)bean屬性創(chuàng)建BeanDefinition對(duì)象,最后將其注冊(cè)到BeanDefinitionRegistry中。創(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è)屬性:
HttpUrl:http請(qǐng)求的baseUrl。CallAdapter:將Call<T>適配為接口方法返回值類型。Converter:將@Body標(biāo)記的方法參數(shù)序列化為請(qǐng)求體數(shù)據(jù);將響應(yīng)體數(shù)據(jù)反序列化為響應(yīng)對(duì)象。OkHttpClient:底層發(fā)送http請(qǐng)求的客戶端對(duì)象。
而構(gòu)建OkHttpClient對(duì)象的時(shí)候,可以注入Interceptor(請(qǐng)求攔截器)和ConnectionPool(連接池)屬性。
因此為了構(gòu)建Retrofit對(duì)象,我們要先創(chuàng)建HttpUrl、CallAdapter、Converter和OkHttpClient;而要構(gòu)建OkHttpClient對(duì)象就得先創(chuàng)建Interceptor和ConnectionPool。
實(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)如下:
RetrofitClientRegistrar
RetrofitClientRegistrar從@RetrofitScan注解中提取出要掃描的基礎(chǔ)包路徑之后,將具體的掃描注冊(cè)邏輯交給了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注解的接口,并注冊(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)是: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è)置為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ì)象。
配置項(xiàng)和 @RetrofitClient為了更加靈活的構(gòu)建Retrofit對(duì)象,我們可以通過配置項(xiàng)以及@RetrofitClient注解屬性傳遞一些動(dòng)態(tài)參數(shù)信息。@RetrofitClient包含的屬性如下:baseUrl:用來創(chuàng)建Retrofit的HttpUrl,表示該接口下所有請(qǐng)求都適用的基礎(chǔ)url。poolName:該接口下請(qǐng)求使用的連接池的名稱,決定了ConnectionPool對(duì)象的取值。connectTimeoutMs/readTimeoutMs/writeTimeoutMs:用于構(gòu)建OkHttpClient對(duì)象的超時(shí)時(shí)間設(shè)置。logLevel/logStrategy:配置該接口下請(qǐng)求的日志打印級(jí)別和日志打印策略,可用來創(chuàng)建日志打印攔截器Interceptor。RetrofitFactoryBean``RetrofitFactoryBean實(shí)現(xiàn)邏輯非常復(fù)雜,概括起來主要包含以下幾點(diǎn):通過配置項(xiàng)數(shù)據(jù)以及 @RetrofitClient注解數(shù)據(jù)完成了Retrofit對(duì)象的構(gòu)建。每一個(gè) HttpService接口就會(huì)構(gòu)建一個(gè)Retrofit對(duì)象,每一個(gè)Retrofit對(duì)象就會(huì)構(gòu)建對(duì)應(yīng)的OkHttpClient對(duì)象。可擴(kuò)展的注解式攔截器是通過 InterceptMark注解標(biāo)記實(shí)現(xiàn)的,路徑攔截匹配是通過BasePathMatchInterceptor實(shí)現(xiàn)的。
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 {
// 接口校驗(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)系刪除! 下面是部分截圖:
推薦閱讀
關(guān)于程序員大白
程序員大白是一群哈工大,東北大學(xué),西湖大學(xué)和上海交通大學(xué)的碩士博士運(yùn)營維護(hù)的號(hào),大家樂于分享高質(zhì)量文章,喜歡總結(jié)知識(shí),歡迎關(guān)注[程序員大白],大家一起學(xué)習(xí)進(jìn)步!


