实现自己的轻量级http调用工具

本文来源:http://8rr.co/SUbF
本篇文章继介绍retrofit-spring-boot-starter的实现原理,从零开始介绍如何在spring-boot项目中基于Retrofit实现自己的轻量级http调用工具。
项目源码:retrofit-spring-boot-starter
确定实现思路
我们首先直接看一下使用retrofit原始API是如何发起一个http请求的。
定义接口
public interface GitHubService {@GET("users/{user}/repos")Call> listRepos(@Path("user") String user);
}
创建接口代理对象
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.github.com/").build();// 实际业务场景构建Retrofit比这复杂多了,这里最简单化处理GitHubService service = retrofit.create(GitHubService.class);
发起请求
Call> repos = service.listRepos("octocat");
可以看到,Retrofit本身已经很好的支持了通过接口发起htp请求。但是如果我们项目每一个业务代码都要写上面的样板代码,会非常的繁琐。有没有一种方式让用户只关注接口定义,其它事情全部交给框架自动处理?这个时候我们可能会联想到spring-boot项目下使用Mybatis,用户只需要定义Mapper接口和书写sql即可,完全不用管与JDBC的交互细节。与之类似,我们最终也要实现让用户只需要定义HttpService接口,不用管其他底层实现细节。
相关知识介绍
为了方便后面的介绍,我们先得了解一下几个相关知识点。
spring容器初始化
我们首先要简单了解一下spring容器初始化。简单来讲,spring容器初始化主要包含以下2个步骤:
注册Bean定义:扫描并解析配置文件或者某些注解得到Bean属性(包括 beanName、beanClassName、scope、isSingleton等等),然后基于这个bean属性创建BeanDefinition对象,最后将其注册到BeanDefinitionRegistry中。创建Bean实例:根据 BeanDefinitionRegistry里面的BeanDefinition信息,创建Bean实例,并将实例对象保存到spring容器中,创建的方式包括反射创建、工厂方法创建和工厂Bean(FactoryBean)创建等等。
当然,实际的spring容器初始化比这复杂的多,考虑到这块不是本文的重点,暂时这么理解就行。
Retrofit对象简介
我们已经知道使用Retrofit对象可以创建接口代理对象,接下来看一下Retrofit的UML类图(只列出了我们关注的依赖):

通过分析UML类图,我们可以发现,构建Retrofit对象的时候,可以注入以下4个属性:
HttpUrl:http请求的baseUrl。CallAdapter:将Call适配为接口方法返回值类型。Converter:将@Body标记的方法参数序列化为请求体数据;将响应体数据反序列化为响应对象。OkHttpClient:底层发送http请求的客户端对象。
而构建OkHttpClient对象的时候,可以注入Interceptor(请求拦截器)和ConnectionPool(连接池)属性。
因此为了构建Retrofit对象,我们要先创建HttpUrl、CallAdapter、Converter和OkHttpClient;而要构建OkHttpClient对象就得先创建Interceptor和ConnectionPool。
实现详解
注册Bean定义
为了实现将HttpService接口代理对象完全交由spring容器管理,首先就得将HttpService接口扫描并注册到BeanDefinitionRegistry中。spring提供了ImportBeanDefinitionRegistrar接口,支持了自定义注册BeanDefinition的功能。因此我们先定义RetrofitClientRegistrar类用来实现上述功能。具体实现如下:
RetrofitClientRegistrarRetrofitClientRegistrar从@RetrofitScan注解中提取出要扫描的基础包路径之后,将具体的扫描注册逻辑交给了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执行ClassPathRetrofitClientScanner scanner = new ClassPathRetrofitClientScanner(registry, classLoader);if (resourceLoader != null) {scanner.setResourceLoader(resourceLoader);}//指定扫描的基础包String[] basePackages = getPackagesToScan(attributes);scanner.registerFilters();// 扫描并注册到BeanDefinitionscanner.doScan(basePackages);}}
ClassPathRetrofitClientScannerClassPathRetrofitClientScanner继承了ClassPathBeanDefinitionScanner,这是Spring提供的类路径下BeanDefinition的扫描器。需要注意的一点是:BeanDefinition的beanClass属性全部设置为了RetrofitFactoryBean.class,同时将接口自身的类型传递到了RetrofitFactoryBean的retrofitInterface属性中。这说明,最终创建Bean实例是通过RetrofitFactoryBean来完成的。
public class ClassPathRetrofitClientScanner extends ClassPathBeanDefinitionScanner {// 省略其它代码private void processBeanDefinitions(SetbeanDefinitions) { 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全部设置为RetrofitFactoryBeandefinition.setBeanClass(RetrofitFactoryBean.class);}}}
这样,我们就完成了扫描指定路径下带有@RetrofitClient注解的接口,并将其注册到BeanDefinitionRegistry的功能了。
@RetrofitClient注解要标识在HttpService的接口上!@RetrofitScan指定了要扫描的包路径。具体可看考源码。
创建Bean实例
上面已经说了创建Bean实例实际上是通过RetrofitFactoryBean实现的。具体就是实现FactoryBean接口,然后重写getObject()方法来完成创建接口Bean实例的逻辑。并且,我们也已经知道通过Retrofit对象能够生成接口代理对象。因此getObject()方法的核心就是构建Retrofit对象,并基于此生成http接口代理对象。
配置项和
@RetrofitClient为了更加灵活的构建Retrofit对象,我们可以通过配置项以及@RetrofitClient注解属性传递一些动态参数信息。@RetrofitClient包含的属性如下:baseUrl:用来创建Retrofit的HttpUrl,表示该接口下所有请求都适用的基础url。poolName:该接口下请求使用的连接池的名称,决定了ConnectionPool对象的取值。connectTimeoutMs/readTimeoutMs/writeTimeoutMs:用于构建OkHttpClient对象的超时时间设置。logLevel/logStrategy:配置该接口下请求的日志打印级别和日志打印策略,可用来创建日志打印拦截器Interceptor。RetrofitFactoryBeanRetrofitFactoryBean实现逻辑非常复杂,概括起来主要包含以下几点:通过配置项数据以及
@RetrofitClient注解数据完成了Retrofit对象的构建。每一个
HttpService接口就会构建一个Retrofit对象,每一个Retrofit对象就会构建对应的OkHttpClient对象。可扩展的注解式拦截器是通过
InterceptMark注解标记实现的,路径拦截匹配是通过BasePathMatchInterceptor实现的。
public class RetrofitFactoryBeanimplements FactoryBean , EnvironmentAware, ApplicationContextAware { // 省略其它代码public RetrofitFactoryBean(ClassretrofitInterface) { 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}
这样,我们就完成了创建HttpServiceBean实例的功能了。在使用的时候直接注入HttpService,然后调用其方法就能发送对应的http请求。
结语
总的来说,在spring-boot项目中基于Retrofit实现自己的轻量级http调用工具的核心只有两点:第一是注册HttpService接口的BeanDefinition,第二就是构建Retrofit来创建HttpService的代理对象。如需了解更多细节,建议直接查看retrofit-spring-boot-starter源码。
- End -
由于微信平台算法改版,公号内容将不再以时间排序展示,如果大家想第一时间看到我们的推送,强烈建议星标我们和给我们多点点【在看】。星标具体步骤为:
(1)点击页面最上方“小詹学Python”,进入公众号主页。
(2)点击右上角的小点点,在弹出页面点击“设为星标”,就可以啦。
感谢支持,比心。
