本文来源:http://8rr.co/SUbF

本篇文章继介绍retrofit-spring-boot-starter的实现原理,从零开始介绍如何在spring-boot项目中基于Retrofit实现自己的轻量级http调用工具

项目源码:retrofit-spring-boot-starter

确定实现思路

我们首先直接看一下使用retrofit原始API是如何发起一个http请求的。

  1. 定义接口

   public interface GitHubService {@GET("users/{user}/repos")Call<List<Repo>> listRepos(@Path("user") String user);}
  1. 创建接口代理对象

   Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.github.com/").build();// 实际业务场景构建Retrofit比这复杂多了,这里最简单化处理GitHubService service = retrofit.create(GitHubService.class);
  1. 发起请求

   Call<List<Repo>> repos = service.listRepos("octocat");

可以看到,Retrofit本身已经很好的支持了通过接口发起htp请求。但是如果我们项目每一个业务代码都要写上面的样板代码,会非常的繁琐。有没有一种方式让用户只关注接口定义,其它事情全部交给框架自动处理?这个时候我们可能会联想到spring-boot项目下使用Mybatis,用户只需要定义Mapper接口和书写sql即可,完全不用管与JDBC的交互细节。与之类似,我们最终也要实现让用户只需要定义HttpService接口,不用管其他底层实现细节

相关知识介绍

为了方便后面的介绍,我们先得了解一下几个相关知识点。

spring容器初始化

我们首先要简单了解一下spring容器初始化。简单来讲,spring容器初始化主要包含以下2个步骤:

  1. 注册Bean定义:扫描并解析配置文件或者某些注解得到Bean属性(包括beanNamebeanClassNamescopeisSingleton等等),然后基于这个bean属性创建BeanDefinition对象,最后将其注册到BeanDefinitionRegistry中。

  2. 创建Bean实例:根据BeanDefinitionRegistry里面的BeanDefinition信息,创建Bean实例,并将实例对象保存到spring容器中,创建的方式包括反射创建、工厂方法创建和工厂Bean(FactoryBean)创建等等。

当然,实际的spring容器初始化比这复杂的多,考虑到这块不是本文的重点,暂时这么理解就行。

Retrofit对象简介

我们已经知道使用Retrofit对象可以创建接口代理对象,接下来看一下Retrofit的UML类图(只列出了我们关注的依赖):

通过分析UML类图,我们可以发现,构建Retrofit对象的时候,可以注入以下4个属性:

  1. HttpUrlhttp请求的baseUrl

  2. CallAdapter:将Call<T>适配为接口方法返回值类型。

  3. Converter:将@Body标记的方法参数序列化为请求体数据;将响应体数据反序列化为响应对象。

  4. OkHttpClient:底层发送http请求的客户端对象。

而构建OkHttpClient对象的时候,可以注入Interceptor(请求拦截器)和ConnectionPool(连接池)属性。

因此为了构建Retrofit对象,我们要先创建HttpUrlCallAdapterConverterOkHttpClient;而要构建OkHttpClient对象就得先创建InterceptorConnectionPool

实现详解

注册Bean定义

为了实现将HttpService接口代理对象完全交由spring容器管理,首先就得将HttpService接口扫描并注册到BeanDefinitionRegistry中。spring提供了ImportBeanDefinitionRegistrar接口,支持了自定义注册BeanDefinition的功能。因此我们先定义RetrofitClientRegistrar类用来实现上述功能。具体实现如下:

  1. RetrofitClientRegistrar

    RetrofitClientRegistrar@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);}}
  1. ClassPathRetrofitClientScanner

    ClassPathRetrofitClientScanner继承了ClassPathBeanDefinitionScanner,这是Spring提供的类路径下BeanDefinition的扫描器。需要注意的一点是:BeanDefinitionbeanClass属性全部设置为了RetrofitFactoryBean.class,同时将接口自身的类型传递到了RetrofitFactoryBeanretrofitInterface属性中。这说明,最终创建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全部设置为RetrofitFactoryBeandefinition.setBeanClass(RetrofitFactoryBean.class);}}}

这样,我们就完成了扫描指定路径下带有@RetrofitClient注解的接口,并将其注册到BeanDefinitionRegistry的功能了

@RetrofitClient注解要标识在HttpService的接口上!@RetrofitScan指定了要扫描的包路径。具体可看考源码。

创建Bean实例

上面已经说了创建Bean实例实际上是通过RetrofitFactoryBean实现的。具体就是实现FactoryBean<T>接口,然后重写getObject()方法来完成创建接口Bean实例的逻辑。并且,我们也已经知道通过Retrofit对象能够生成接口代理对象。因此getObject()方法的核心就是构建Retrofit对象,并基于此生成http接口代理对象。

  1. 配置项和@RetrofitClient 为了更加灵活的构建Retrofit对象,我们可以通过配置项以及@RetrofitClient注解属性传递一些动态参数信息。@RetrofitClient包含的属性如下:

    1. baseUrl:用来创建RetrofitHttpUrl,表示该接口下所有请求都适用的基础url

    2. poolName:该接口下请求使用的连接池的名称,决定了ConnectionPool对象的取值。

    3. connectTimeoutMs/readTimeoutMs/writeTimeoutMs:用于构建OkHttpClient对象的超时时间设置。

    4. logLevel/logStrategy:配置该接口下请求的日志打印级别和日志打印策略,可用来创建日志打印拦截器Interceptor

  2. RetrofitFactoryBean RetrofitFactoryBean实现逻辑非常复杂,概括起来主要包含以下几点:

    1. 通过配置项数据以及@RetrofitClient注解数据完成了Retrofit对象的构建。

    2. 每一个HttpService接口就会构建一个Retrofit对象,每一个Retrofit对象就会构建对应的OkHttpClient对象。

    3. 可扩展的注解式拦截器是通过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}

这样,我们就完成了创建HttpServiceBean实例的功能了。在使用的时候直接注入HttpService,然后调用其方法就能发送对应的http请求。

结语

总的来说,在spring-boot项目中基于Retrofit实现自己的轻量级http调用工具的核心只有两点:第一是注册HttpService接口的BeanDefinition,第二就是构建Retrofit来创建HttpService的代理对象。如需了解更多细节,建议直接查看retrofit-spring-boot-starter源码。

- End -

由于微信平台算法改版,公号内容将不再以时间排序展示,如果大家想第一时间看到我们的推送,强烈建议星标我们和给我们多点点【在看】。星标具体步骤为:

(1)点击页面最上方“小詹学Python”,进入公众号主页。
(2)点击右上角的小点点,在弹出页面点击“设为星标”,就可以啦。
感谢支持,比心。

实现自己的轻量级http调用工具相关推荐

  1. 自己动手,实现一款轻量级 HTTP 调用工具

    今日推荐 吊打 ThreadLocal,谈谈FastThreadLocal为啥能这么快?一个Github项目搞定微信.QQ.支付宝等第三方登录注解+反射优雅的实现Excel导入导出(通用版)Fluen ...

  2. Containerd【轻量级容器管理工具】

    文章目录 Containerd[轻量级容器管理工具] 一.Containerd介绍 1.Containerd的由来 2.Containerd概念 3.Containerd架构 4.几个概念区分 二.C ...

  3. 腾讯TEG团队打造轻量级数据可视化工具——小马BI【强烈推荐大数据行业学习】

    推荐一个BI工具--小马BI 在职场中有一项共识是:数据驱动业务价值.业务在产品.运营.开发.技术支持.销售等环节都有着大量的数据需求, 市面上也出现了很多 BI 可视化工具,但如果能同时具备以下特性 ...

  4. 推荐轻量级MySQL数据库管理工具 Adminer

    服务器上一直用 phpMyAdmin 管理 MySQL 数据库, 有点囊肿, 配置也麻烦, 所以换了个轻量级的 Adminer 数据库管理替换. Adminer 就一个单文件, 知道什么就轻量级了吧, ...

  5. 嵌入式linux轻量级sshd服务工具Dropbear移植

    嵌入式linux轻量级sshd服务工具Dropbear移植 介绍 编译流程 1. 编译zlib 2. 编译dropbear 3. 移植 4. 运行 5. 移植sftp-server 介绍   往往考虑 ...

  6. 分享一个轻量级的屏幕截图工具-Lightshot

    https://app.prntscr.com/en/index.html Lightshot 是一个轻量级的屏幕截图工具,功能有限但有用.用户可以使用这款工具截屏然后拖选出合适的区域大小.拖选完成后 ...

  7. Android轻量级Layout Inspector工具

    效果图 若想使用该工具,请点我 为什么要做这个工具 如果有更好的轮子用,谁愿意造轮子. 在21年年初的时候,我被调到公司的另外一个项目,这个项目有几个特点:历史非常的悠久,使用的技术落后,项目工程复杂 ...

  8. 基于 Hessian 轻量级远程调用的原理及示例

    1 简介 Hessian 是 Caucho 公司开发的一种基于二进制 RPC 协议(Remote Procedure Call protocol)的轻量级远程调用框架,其使用简单的方法提供了 RMI ...

  9. RunC 轻量级 容器运行工具 简介

    RunC 是什么? RunC 是一个轻量级的工具,它是用来运行容器的,只用来做这一件事,并且这一件事要做好.我们可以认为它就是个命令行小工具,可以不用通过 docker 引擎,直接运行容器.事实上,r ...

最新文章

  1. 同济大学计算机云南2019,同济大学录取分数线2019(在各省市录取数据)
  2. 非线性调频 matlab,非线性调频信号
  3. 东北师范大学计算机科学与技术学科评估,东北的大学最强十校,工科是真强,2所211大学无缘前十...
  4. C# 获取指定进程的主窗口句柄
  5. linux lvs公网ip,Linux集群架构(2)LVS介绍、LVS的调度算法、NAT模式搭建、 DR模式、keepalive...
  6. cp 提示 overwrite 问题
  7. mssql php 5.4,PHP5.4如何连接MSSql Server2005
  8. Spring4 事务管理
  9. java number 转 long_Java Number.longValue()用法及代码示例
  10. GHOST文件修改OEM的方法
  11. React Antd4 CRA / Next.js / Vite 按需加载组件的 CSS / Less
  12. 给出某个时间段,要求以三十分钟为分割,统计出每三十分钟内数据的数量
  13. 【蓝桥杯】欧拉定理 + 欧拉数线性筛法
  14. 海康ISAPI透传ftp
  15. BP神经网络求解异或算法
  16. 软件测试——Docker基本命令汇总
  17. Linux下的压测工具 hey
  18. UG10.0汽车大模钢料编程带刀路3D图档
  19. 如何计算两个坐标点的方位角
  20. ae渲染存在偏移_(图文+视频)C4D+AE野教程:一起来制作一个MG方块动画吧

热门文章

  1. centos7 安装python3.7.1(亲测下面的安装依赖包)
  2. 第五章 常用Lua开发库3-模板渲染
  3. MySQL重要概念图解(重要)
  4. 程序猿眼里的高并发架构
  5. PHP中判断字符串是否全是中文eregi函数或含有中文preg_match函数
  6. PHP算法导出Excel实现字段联动
  7. matlab删失数据威布尔,基于混合I型删失数据威布尔模型的可接受抽样计划
  8. CentOS 7 解决丢失 nginx.pid 1
  9. dseo13b打开自动消失_抖音怎么自动生成字幕?动态字幕有哪几种?
  10. pipe 半双工_pipe 半双工_Linux管道PIPE的原理和应用