在《spring-boot項目最優雅的http客戶端工具,用它就夠了,太香了!》這篇文章中,咱們知道了retrofit-spring-boot-starter
的使用方式。本篇文章繼續繼續介紹retrofit-spring-boot-starter
的實現原理,從零開始介紹如何在spring-boot項目中基於Retrofit實現本身的輕量級http調用工具。java
項目源碼:retrofit-spring-boot-startergit
咱們首先直接看一下使用retrofit
原始API是如何發起一個http請求的。github
定義接口spring
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
複製代碼
建立接口代理對象sql
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build();
// 實際業務場景構建Retrofit比這複雜多了,這裏最簡單化處理
GitHubService service = retrofit.create(GitHubService.class);
複製代碼
發起請求api
Call<List<Repo>> repos = service.listRepos("octocat");
複製代碼
能夠看到,Retrofit
自己已經很好的支持了經過接口發起htp
請求。可是若是咱們項目每個業務代碼都要寫上面的樣板代碼,會很是的繁瑣。有沒有一種方式讓用戶只關注接口定義,其它事情所有交給框架自動處理?這個時候咱們可能會聯想到spring-boot
項目下使用Mybatis
,用戶只須要定義Mapper
接口和書寫sql
便可,徹底不用管與JDBC
的交互細節。與之相似,咱們最終也要實現讓用戶只須要定義HttpService
接口,不用管其餘底層實現細節。markdown
爲了方便後面的介紹,咱們先得了解一下幾個相關知識點。app
咱們首先要簡單瞭解一下spring
容器初始化。簡單來說,spring
容器初始化主要包含如下2個步驟:框架
beanName
、beanClassName
、scope
、isSingleton
等等),而後基於這個bean
屬性建立BeanDefinition
對象,最後將其註冊到BeanDefinitionRegistry
中。BeanDefinitionRegistry
裏面的BeanDefinition
信息,建立Bean實例,並將實例對象保存到spring
容器中,建立的方式包括反射建立、工廠方法建立和工廠Bean(FactoryBean
)建立等等。固然,實際的spring
容器初始化比這複雜的多,考慮到這塊不是本文的重點,暫時這麼理解就行。ide
Retrofit
對象簡介咱們已經知道使用Retrofit
對象能夠建立接口代理對象,接下來看一下Retrofit
的UML類圖(只列出了咱們關注的依賴): 經過分析UML類圖,咱們能夠發現,構建
Retrofit
對象的時候,能夠注入如下4個屬性:
HttpUrl
:http
請求的baseUrl
。CallAdapter
:將Call<T>
適配爲接口方法返回值類型。Converter
:將@Body
標記的方法參數序列化爲請求體數據;將響應體數據反序列化爲響應對象。OkHttpClient
:底層發送http
請求的客戶端對象。而構建OkHttpClient
對象的時候,能夠注入Interceptor
(請求攔截器)和ConnectionPool
(鏈接池)屬性。
所以爲了構建Retrofit
對象,咱們要先建立HttpUrl
、CallAdapter
、Converter
和OkHttpClient
;而要構建OkHttpClient
對象就得先建立Interceptor
和ConnectionPool
。
爲了實現將HttpService
接口代理對象徹底交由spring
容器管理,首先就得將HttpService
接口掃描並註冊到BeanDefinitionRegistry
中。spring
提供了ImportBeanDefinitionRegistrar
接口,支持了自定義註冊BeanDefinition
的功能。所以咱們先定義RetrofitClientRegistrar
類用來實現上述功能。具體實現以下:
RetrofitClientRegistrar
RetrofitClientRegistrar
從@RetrofitScan
註解中提取出要掃描的基礎包路徑以後,將具體的掃描註冊邏輯交給了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執行
ClassPathRetrofitClientScanner scanner = new ClassPathRetrofitClientScanner(registry, classLoader);
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
//指定掃描的基礎包
String[] basePackages = getPackagesToScan(attributes);
scanner.registerFilters();
// 掃描並註冊到BeanDefinition
scanner.doScan(basePackages);
}
}
複製代碼
ClassPathRetrofitClientScanner
ClassPathRetrofitClientScanner
繼承了ClassPathBeanDefinitionScanner
,這是Spring提供的類路徑下BeanDefinition
的掃描器。須要注意的一點是:BeanDefinition
的beanClass
屬性所有設置爲了RetrofitFactoryBean.class
,同時將接口自身的類型傳遞到了RetrofitFactoryBean
的retrofitInterface
屬性中。這說明,最終建立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所有設置爲RetrofitFactoryBean
definition.setBeanClass(RetrofitFactoryBean.class);
}
}
}
複製代碼
這樣,咱們就完成了掃描指定路徑下帶有@RetrofitClient
註解的接口,並將其註冊到BeanDefinitionRegistry
的功能了。
@RetrofitClient
註解要標識在HttpService
的接口上!@RetrofitScan
指定了要掃描的包路徑。具體可看考源碼。
上面已經說了建立Bean實例其實是經過RetrofitFactoryBean
實現的。具體就是實現FactoryBean<T>
接口,而後重寫getObject()
方法來完成建立接口Bean實例的邏輯。而且,咱們也已經知道經過Retrofit
對象可以生成接口代理對象。所以getObject()
方法的核心就是構建Retrofit
對象,並基於今生成http
接口代理對象。
配置項和@RetrofitClient
爲了更加靈活的構建Retrofit
對象,咱們能夠經過配置項以及@RetrofitClient
註解屬性傳遞一些動態參數信息。@RetrofitClient
包含的屬性以下:
baseUrl
:用來建立Retrofit
的HttpUrl
,表示該接口下全部請求都適用的基礎url
。poolName
:該接口下請求使用的鏈接池的名稱,決定了ConnectionPool
對象的取值。connectTimeoutMs/readTimeoutMs/writeTimeoutMs
:用於構建OkHttpClien
t對象的超時時間設置。logLevel/logStrategy
:配置該接口下請求的日誌打印級別和日誌打印策略,可用來建立日誌打印攔截器Interceptor
。RetrofitFactoryBean
RetrofitFactoryBean
實現邏輯很是複雜,歸納起來主要包含如下幾點:
@RetrofitClient
註解數據完成了Retrofit
對象的構建。HttpService
接口就會構建一個Retrofit
對象,每個Retrofit
對象就會構建對應的OkHttpClient
對象。InterceptMark
註解標記實現的,路徑攔截匹配是經過BasePathMatchInterceptor
實現的。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);
// 構建Retrofit對象
Retrofit retrofit = getRetrofit(retrofitInterface);
// 基於Retrofit建立接口代理對象
return retrofit.create(retrofitInterface);
}
/** * 獲取OkHttpClient實例,一個接口接口對應一個OkHttpClient * * @param retrofitClientInterfaceClass retrofitClient接口類 * @return OkHttpClient實例 */
private synchronized OkHttpClient getOkHttpClient(Class<?> retrofitClientInterfaceClass) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// 基於各類條件構建OkHttpClient
}
/** * 獲取Retrofit實例,一個retrofitClient接口對應一個Retrofit實例 * * @param retrofitClientInterfaceClass retrofitClient接口類 * @return Retrofit實例 */
private synchronized Retrofit getRetrofit(Class<?> retrofitClientInterfaceClass) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// 構建retrofit
}
複製代碼
這樣,咱們就完成了建立HttpService
Bean實例的功能了。在使用的時候直接注入HttpService
,而後調用其方法就能發送對應的http
請求。
總的來講,在spring-boot項目中基於Retrofit實現本身的輕量級http調用工具的核心只有兩點:第一是註冊HttpService
接口的BeanDefinition
,第二就是構建Retrofit
來建立HttpService
的代理對象。如需瞭解更多細節,建議直接查看retrofit-spring-boot-starter源碼。
原創不易,以爲文章寫得不錯的小夥伴,點個贊👍 鼓勵一下吧~
歡迎關注個人開源項目:一款適用於SpringBoot的輕量級HTTP調用框架