自己動手,實現(xiàn)一款輕量級 HTTP 調(diào)用工具
吊打 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個步驟:
注冊Bean定義:掃描并解析配置文件或者某些注解得到Bean屬性(包括
beanName、beanClassName、scope、isSingleton等等),然后基于這個bean屬性創(chuàng)建BeanDefinition對象,最后將其注冊到BeanDefinitionRegistry中。創(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個屬性:
HttpUrl:http請求的baseUrl。CallAdapter:將Call<T>適配為接口方法返回值類型。Converter:將@Body標記的方法參數(shù)序列化為請求體數(shù)據(jù);將響應(yīng)體數(shù)據(jù)反序列化為響應(yīng)對象。OkHttpClient:底層發(fā)送http請求的客戶端對象。
而構(gòu)建OkHttpClient對象的時候,可以注入Interceptor(請求攔截器)和ConnectionPool(連接池)屬性。
因此為了構(gòu)建Retrofit對象,我們要先創(chuàng)建HttpUrl、CallAdapter、Converter和OkHttpClient;而要構(gòu)建OkHttpClient對象就得先創(chuàng)建Interceptor和ConnectionPool。
實現(xiàn)詳解
注冊Bean定義
為了實現(xiàn)將HttpService接口代理對象完全交由spring容器管理,首先就得將HttpService接口掃描并注冊到BeanDefinitionRegistry中。
spring提供了ImportBeanDefinitionRegistrar接口,支持了自定義注冊BeanDefinition的功能。因此我們先定義RetrofitClientRegistrar類用來實現(xiàn)上述功能。具體實現(xiàn)如下:
RetrofitClientRegistrar
RetrofitClientRegistrar從@RetrofitScan注解中提取出要掃描的基礎(chǔ)包路徑之后,將具體的掃描注冊邏輯交給了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執(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的掃描器。
需要注意的一點是:BeanDefinition的beanClass屬性全部設(shè)置為了RetrofitFactoryBean.class,同時將接口自身的類型傳遞到了RetrofitFactoryBean的retrofitInterface屬性中。這說明,最終創(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接口代理對象。
配置項和 @RetrofitClient為了更加靈活的構(gòu)建Retrofit對象,我們可以通過配置項以及@RetrofitClient注解屬性傳遞一些動態(tài)參數(shù)信息。@RetrofitClient包含的屬性如下:baseUrl:用來創(chuàng)建Retrofit的HttpUrl,表示該接口下所有請求都適用的基礎(chǔ)url。poolName:該接口下請求使用的連接池的名稱,決定了ConnectionPool對象的取值。connectTimeoutMs/readTimeoutMs/writeTimeoutMs:用于構(gòu)建OkHttpClient對象的超時時間設(shè)置。logLevel/logStrategy:配置該接口下請求的日志打印級別和日志打印策略,可用來創(chuàng)建日志打印攔截器Interceptor。RetrofitFactoryBean``RetrofitFactoryBean實現(xiàn)邏輯非常復(fù)雜,概括起來主要包含以下幾點:通過配置項數(shù)據(jù)以及 @RetrofitClient注解數(shù)據(jù)完成了Retrofit對象的構(gòu)建。每一個 HttpService接口就會構(gòu)建一個Retrofit對象,每一個Retrofit對象就會構(gòu)建對應(yīng)的OkHttpClient對象。可擴展的注解式攔截器是通過 InterceptMark注解標記實現(xiàn)的,路徑攔截匹配是通過BasePathMatchInterceptor實現(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 {
// 接口校驗
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,即可獲取。

