今日推荐

吊打 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个步骤:

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

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

当然,实际的spring容器初始化比这复杂的多,考虑到这块不是本文的重点,暂时这么理解就行。推荐:Java进阶视频资源

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);}
}

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的功能了。推荐:Java进阶视频资源

@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源码。

欢迎一键三连

推荐一些很不错的计算机学习教程,包括:数据结构、算法、计算机网络、操作系统、Java(spring、springmvc、springboot、springcloud等)等等 ,全部收集于网络,如果有侵权,请联系删除!
下面是部分截图:获取方式点击下方公众号,回复:好好学Java,即可获取。

自己动手,实现一款轻量级 HTTP 调用工具相关推荐

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

    本文来源:http://8rr.co/SUbF 本篇文章继介绍retrofit-spring-boot-starter的实现原理,从零开始介绍如何在spring-boot项目中基于Retrofit实现 ...

  2. Rope一款轻量级别的ETL工具

    介绍 Rope 是一款轻量级别的ETL(Extract-Transform-Load)工具.主要用于从不同源获取/接受数据,然后统一处理数据后,写入到各种目标源:系统采用多级缓冲和数据缓存,每秒可处理 ...

  3. linux本地agent执行脚本_github 4.4K星|马哥教育企业教练团队研发一款轻量级、无Agent自动化运维平台...

    马哥教育企业教练团队研发了一款自动化运维平台系统-Spug,上线后广受中小运维爱好者喜爱,目前github4.4k星,已经成为自动化热门项目.2020年了,运维不会搞运维自动化,都不好意思说自己做运维 ...

  4. 一款轻量级的桌面WebServer通讯组件

    作者:肖波 个人博客:http://blog.csdn.net/eaglet ; http://www.cnblogs.com/eaglet 2007/7 南京 背景 随着互联网的不断发展,传统的We ...

  5. 别再用硬编码写业务流程了,试试这款轻量级流程编排框架

    前言 在每个公司的系统中,总有一些拥有复杂业务逻辑的系统,这些系统承载着核心业务逻辑,几乎每个需求都和这些核心业务有关,这些核心业务业务逻辑冗长,涉及内部逻辑运算,缓存操作,持久化操作,外部资源调取, ...

  6. 从零破解一款轻量级滑动验证码

    关注下方「前端热榜」,回复 "思维图" 获取公众号所有JS思维图 昨天在掘金看到推荐文章<从零开发一款轻量级滑动验证码插件>[1],介绍了一些相关验证码及框架开发的知识 ...

  7. 自己动手完成一款简易P2P共享文件软件的制作(一)

    文章目录 1. 前言 2. 系统总体框架 3. 服务器设计 本文实验测试部分可参考基于QT的一款P2P共享文件系统 源码包下载地址基于QT的一款P2P共享文件系统下载,想要免费获取可以私信我 Gith ...

  8. Forest一款轻量级HTTP客户端框架

    Forest一款轻量级HTTP客户端框架 Forest Forest特性 Forest工作原理 Forest架构 HttpClient Okhttp Forest Forest 是一个开源的 Java ...

  9. 自己动手设计一款iOS自动构建发布工具

    2019独角兽企业重金招聘Python工程师标准>>> 自己动手设计一款iOS自动构建发布工具 一.引言 在iOS开发中,你可能经常会遇到这样的场景: 自己负责的功能模块开发完成后, ...

最新文章

  1. 机器学习中常见的损失函数
  2. Python入门练习题目
  3. hadoop win环境依赖winutils.exe等的gitee下载地址
  4. 牛客NOIP2021提高组OI赛前模拟赛第一场T3——与巨(数学)
  5. 适合小白的Python学习大纲
  6. 可定制的PHP缩略图生成程式(需要GD库支持)
  7. C++流水线的简易实现
  8. 快速排序—三路快排 vs 双基准
  9. Python3.x:pyodbc调用sybase的存储过程
  10. Atitit.注解解析(1)---------词法分析 attilax总结 java .net
  11. 海康威视SDK基于JAVA二次开发
  12. 树莓派——CSI摄像头和USB摄像头的配置与调试
  13. mac关闭虚拟内存_为什么不应该关闭Mac上的虚拟内存
  14. 服装尺寸 html,史上最完整的服装尺寸号型和换算知识
  15. php左斜线和右斜线,左斜杠和右斜杠分别有什么意义?
  16. 如何微信分享网页链接自定义图片和文字描述?生成微信自定义卡片链接流程(附教程与工具)
  17. 微信支付要租用服务器吗,切记!使用微信支付一定要打开这些功能!
  18. PostgreSQL 12 中文文档
  19. Ubuntu最全问题汇总(好东西分享了)
  20. 洛谷 P1460 健康的荷斯坦奶牛 Healthy Holsteins

热门文章

  1. 时间序列预测之二:灰色模型
  2. Bech32编码 (1)产生背景
  3. 百度超级链XChain(2)p2p网络
  4. Html 教程 (1)简介
  5. C++ Primer 5th笔记(chap 14 重载运算和类型转换)重载运算概述
  6. 基于SpringBoot和Vue的分布式爬虫系统(JavaWeb)
  7. git/gitee操作手册
  8. HTTP与HTTPS——密码学笔记(九)
  9. Java分割由多个空白字符连接的字符串
  10. 路由器固件下的小试牛刀,与漏洞相关的经验分享