自己动手,实现一款轻量级 HTTP 调用工具
今日推荐
吊打 ThreadLocal,谈谈FastThreadLocal为啥能这么快?一个Github项目搞定微信、QQ、支付宝等第三方登录注解+反射优雅的实现Excel导入导出(通用版)Fluent Mybatis 牛逼!Nginx 常用配置清单这玩意比ThreadLocal叼多了,吓得我赶紧分享出来。
来源:juejin.cn/post/6854573219899244551
本篇文章继续继续介绍retrofit-spring-boot-starter
的实现原理,从零开始介绍如何在spring-boot项目中基于Retrofit实现自己的轻量级http调用工具。
项目源码:
https://github.com/LianjiaTech/retrofit-spring-boot-starter
确定实现思路
我们首先直接看一下使用retrofit原始API是如何发起一个http请求的。
1.定义接口
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}
2.创建接口代理对象
Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.github.com/").build();// 实际业务场景构建Retrofit比这复杂多了,这里最简单化处理GitHubService service = retrofit.create(GitHubService.class);
3.发起请求
Call<List<Repo>> 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
容器初始化比这复杂的多,考虑到这块不是本文的重点,暂时这么理解就行。推荐:Java进阶视频资源
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
。
实现详解
注册Bean定义
为了实现将HttpService
接口代理对象完全交由spring
容器管理,首先就得将HttpService
接口扫描并注册到BeanDefinitionRegistry
中。
spring
提供了ImportBeanDefinitionRegistrar
接口,支持了自定义注册BeanDefinition
的功能。因此我们先定义RetrofitClientRegistrar
类用来实现上述功能。具体实现如下:
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);}
}
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全部设置为RetrofitFactoryBeandefinition.setBeanClass(RetrofitFactoryBean.class);}}
}
这样,我们就完成了扫描指定路径下带有@RetrofitClient
注解的接口,并将其注册到BeanDefinitionRegistry
的功能了。推荐:Java进阶视频资源
@RetrofitClient
注解要标识在HttpService
的接口上!@RetrofitScan
指定了要扫描的包路径。具体可看考源码。
创建Bean实例
上面已经说了创建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源码。
欢迎一键三连
推荐一些很不错的计算机学习教程,包括:数据结构、算法、计算机网络、操作系统、Java(spring、springmvc、springboot、springcloud等)等等 ,全部收集于网络,如果有侵权,请联系删除!
下面是部分截图:获取方式点击下方公众号,回复:好好学Java,即可获取。
自己动手,实现一款轻量级 HTTP 调用工具相关推荐
- 实现自己的轻量级http调用工具
本文来源:http://8rr.co/SUbF 本篇文章继介绍retrofit-spring-boot-starter的实现原理,从零开始介绍如何在spring-boot项目中基于Retrofit实现 ...
- Rope一款轻量级别的ETL工具
介绍 Rope 是一款轻量级别的ETL(Extract-Transform-Load)工具.主要用于从不同源获取/接受数据,然后统一处理数据后,写入到各种目标源:系统采用多级缓冲和数据缓存,每秒可处理 ...
- linux本地agent执行脚本_github 4.4K星|马哥教育企业教练团队研发一款轻量级、无Agent自动化运维平台...
马哥教育企业教练团队研发了一款自动化运维平台系统-Spug,上线后广受中小运维爱好者喜爱,目前github4.4k星,已经成为自动化热门项目.2020年了,运维不会搞运维自动化,都不好意思说自己做运维 ...
- 一款轻量级的桌面WebServer通讯组件
作者:肖波 个人博客:http://blog.csdn.net/eaglet ; http://www.cnblogs.com/eaglet 2007/7 南京 背景 随着互联网的不断发展,传统的We ...
- 别再用硬编码写业务流程了,试试这款轻量级流程编排框架
前言 在每个公司的系统中,总有一些拥有复杂业务逻辑的系统,这些系统承载着核心业务逻辑,几乎每个需求都和这些核心业务有关,这些核心业务业务逻辑冗长,涉及内部逻辑运算,缓存操作,持久化操作,外部资源调取, ...
- 从零破解一款轻量级滑动验证码
关注下方「前端热榜」,回复 "思维图" 获取公众号所有JS思维图 昨天在掘金看到推荐文章<从零开发一款轻量级滑动验证码插件>[1],介绍了一些相关验证码及框架开发的知识 ...
- 自己动手完成一款简易P2P共享文件软件的制作(一)
文章目录 1. 前言 2. 系统总体框架 3. 服务器设计 本文实验测试部分可参考基于QT的一款P2P共享文件系统 源码包下载地址基于QT的一款P2P共享文件系统下载,想要免费获取可以私信我 Gith ...
- Forest一款轻量级HTTP客户端框架
Forest一款轻量级HTTP客户端框架 Forest Forest特性 Forest工作原理 Forest架构 HttpClient Okhttp Forest Forest 是一个开源的 Java ...
- 自己动手设计一款iOS自动构建发布工具
2019独角兽企业重金招聘Python工程师标准>>> 自己动手设计一款iOS自动构建发布工具 一.引言 在iOS开发中,你可能经常会遇到这样的场景: 自己负责的功能模块开发完成后, ...
最新文章
- 机器学习中常见的损失函数
- Python入门练习题目
- hadoop win环境依赖winutils.exe等的gitee下载地址
- 牛客NOIP2021提高组OI赛前模拟赛第一场T3——与巨(数学)
- 适合小白的Python学习大纲
- 可定制的PHP缩略图生成程式(需要GD库支持)
- C++流水线的简易实现
- 快速排序—三路快排 vs 双基准
- Python3.x:pyodbc调用sybase的存储过程
- Atitit.注解解析(1)---------词法分析 attilax总结 java .net
- 海康威视SDK基于JAVA二次开发
- 树莓派——CSI摄像头和USB摄像头的配置与调试
- mac关闭虚拟内存_为什么不应该关闭Mac上的虚拟内存
- 服装尺寸 html,史上最完整的服装尺寸号型和换算知识
- php左斜线和右斜线,左斜杠和右斜杠分别有什么意义?
- 如何微信分享网页链接自定义图片和文字描述?生成微信自定义卡片链接流程(附教程与工具)
- 微信支付要租用服务器吗,切记!使用微信支付一定要打开这些功能!
- PostgreSQL 12 中文文档
- Ubuntu最全问题汇总(好东西分享了)
- 洛谷 P1460 健康的荷斯坦奶牛 Healthy Holsteins