【网络编程】从Retrofit原理来看HTTP

文章目录

  • create()
    • `validateServiceInterface()`
    • 动态代理
    • `loadServiceMethod(method)`

create()

让我们先深入到create 里,

public <T> T create(final Class<T> service) {validateServiceInterface(service);....
}

validateServiceInterface()

我们发现首先会执行validateServiceInterface(),名字意思为验证服务接口,传入的参数也是接口所对应的类文件。

	private void validateServiceInterface(Class<?> service) {// 1. 检查传入的参数是否为接口if (!service.isInterface()) {throw new IllegalArgumentException("API declarations must be interfaces.");}// 2. 使用双端队列来存储待检查的接口Deque<Class<?>> check = new ArrayDeque<>(1);check.add(service);while (!check.isEmpty()) {// 从队列中取出接口Class<?> candidate = check.removeFirst();// 检查接口是否有泛型参数if (candidate.getTypeParameters().length != 0) {StringBuilder message =new StringBuilder("Type parameters are unsupported on ").append(candidate.getName());if (candidate != service) {message.append(" which is an interface of ").append(service.getName());}// 如果接口包含泛型参数,则抛出异常throw new IllegalArgumentException(message.toString());}// 将接口的父接口添加到检查队列中Collections.addAll(check, candidate.getInterfaces());}// 3. 如果设置了validateEagerly标志为true,则进行进一步的验证if (validateEagerly) {Platform platform = Platform.get();// 遍历接口中声明的方法for (Method method : service.getDeclaredMethods()) {// 检查方法是否为默认方法且不是静态方法if (!platform.isDefaultMethod(method) && !Modifier.isStatic(method.getModifiers())) {// 加载服务方法loadServiceMethod(method);}}}}

我们可以分三部分看看他是如何验证接口的:

  • 第一部分简单判断这个类引用是否为接口(!service.isInterface()),否则抛出异常.
  • 第二部分,这里是先创建了一个双向队列(ArrayDeque),然后并将这个service添加到队列中。然后进入一个循环,直到队列为空。在每次循环中,从队列的开头取出一个service用于检查。如果发现有一个元素含泛型(candidate.getTypeParameters().length != 0),则抛出异常.提示"Type parameters are unsupported on …",其中包含相应类的名称。如果取出来的service不是初始的service类,错误消息还会指示它是service的接口之一。最后,将candidate的所有接口添加到队列中,以便进一步检查它们是否有类型参数。
  • 第三部分我们看到了一个validateEagerly的标志位(激进验证),如果标志位validateEagerly为真,即需要进行全面验证,则获取当前平台(Platform)的实例。然后,遍历service类声明的所有方法。对于不是默认方法(default method)且不是静态方法的每个方法(Retrofit不支持或不认为是),调用loadServiceMethod方法进行加载,遍历方法进行加载,就算验证的一部分了。
  • 在我们使用Retrofit时,我们建立的xxxService接口里的方法在第一次加载时会进行初始化操作,在create()中,就会进行,这样更早的快速暴露问题,但是在初始化的过程中也会进行一些反射的内容,很耗时。

动态代理

接下来让我们看create()剩下部分的实现

	public <T> T create(final Class<T> service) {// 验证服务接口的有效性validateServiceInterface(service);return (T) Proxy.newProxyInstance(service.getClassLoader(),new Class<?>[] { service },new InvocationHandler() {private final Platform platform = Platform.get();private final Object[] emptyArgs = new Object[0];@Overridepublic @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)throws Throwable {// 如果方法是 Object 类的方法,则执行普通的调用if (method.getDeclaringClass() == Object.class) {return method.invoke(this, args);}args = args != null ? args : emptyArgs;// 如果方法是默认方法(Java 8 及以上版本),则调用默认方法实现return platform.isDefaultMethod(method)? platform.invokeDefaultMethod(method, service, proxy, args): loadServiceMethod(method).invoke(args);}});}

可以看出来后面的内容只是一个方法的调用:newProxyInstance() 动态代理
有三个参数,

  • service.getClassLoader() 简单获取的类加载器
  • new Class<?>[] { service } 动态代理的接口
  • new InvocationHandler() 后边代理实例调用方法
    所以我们只需要看真正的实现,真正干事的部分,即代理实例调用方法的部分的invoke()里,在实际操作里,会执行对应Retrofit生成的动态代理类的invocationHandler.invoke()
       	 @Overridepublic @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)throws Throwable {// 如果方法是 Object 类的方法,则直接 执行普通的调用,不会代理if (method.getDeclaringClass() == Object.class) {return method.invoke(this, args);}args = args != null ? args : emptyArgs;// 如果方法是默认方法(Java 8 及以上版本),则调用默认方法实现return platform.isDefaultMethod(method)? platform.invokeDefaultMethod(method, service, proxy, args): loadServiceMethod(method).invoke(args);}}

老版本Android不支持Java8,也不知道什么叫默认方法

	@IgnoreJRERequirement // Only called on API 24+.boolean isDefaultMethod(Method method) {return hasJava8Types && method.isDefault();//会检测是否有java8 对应的特性,那我直接返回false}

那么核心代码从invoke()走到了loadServiceMethod(method).invoke(args);
里边的args : args = args != null ? args : emptyArgs;


loadServiceMethod(method)

ServiceMethod<?> loadServiceMethod(Method method) {// 1首先尝试从缓存serviceMethodCache中获取已存在的 ServiceMethod 对象ServiceMethod<?> result = serviceMethodCache.get(method);if (result != null) return result;// 2如果缓存中不存在,则需要进行同步操作,确保线程安全synchronized (serviceMethodCache) {// 2再次尝试从缓存中获取 ServiceMethod 对象result = serviceMethodCache.get(method);if (result == null) {// 如果缓存中依然不存在,则需要解析注解并创建 ServiceMethod 对象result = ServiceMethod.parseAnnotations(this, method);// 将新创建的 ServiceMethod 对象添加到缓存中serviceMethodCache.put(method, result);}}return result;
}

整个过程就是一个带缓存(serviceMethodCache)的加载,缓存中有直接返回,没有则先进行创建,再放到缓存中然后返回.

serviceMethodCache是Retrofit2的一个缓存机制(实际上是Map),用于缓存已经解析的ServiceMethod对象,ServiceMethod 对象代表了一个被注解修饰的接口方法,每一个接口方法都有对应。(毕竟解析注解很耗时)

所以此处的核心代码其实是如何创建对应的ServiceMethod,也就是:

result = ServiceMethod.parseAnnotations(this, method);

我们继续深入跟进:

abstract class ServiceMethod<T> {static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {// 第1步:解析方法的注解,创建RequestFactory对象,RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);// 第2步:获取方法的返回类型Type returnType = method.getGenericReturnType();// 第3步:检查返回类型是否包含类型变量或通配符if (Utils.hasUnresolvableType(returnType)) {throw methodError(method,"方法的返回类型不能包含类型变量或通配符:%s",returnType);}// 第4步:检查返回类型是否为voidif (returnType == void.class) {throw methodError(method, "服务方法不能返回void。");}// 第5步:解析HTTP服务方法特定的注解return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);}//使用给定的参数调用服务方法,并返回结果。。。abstract @Nullable T invoke(Object[] args);
}

我们发现:

  • 调用了RequestFactory.parseAnnotations(retrofit, method);,并保存返回值,这里是工厂模式
  • HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  • 下边的invoke()很眼熟,这不正好是前边loadServiceMethod(method).invoke(args);的吗

核心代码是第1步和第5步,该方法的目的还是获取Service接口所对应的ServiceMethod<T>

最后返回的还是HttpServiceMethod 的方法。即HttpServiceMethod.parseAnnotations()

那么既然转换注解还是靠该类实现的,那么最最重要的最终的invoke()是不是也是一个该类实例呢?

我们发现点进去后是:abstract class HttpServiceMethod<ResponseT, ReturnT> extends ServiceMethod<ReturnT>
这是一个继承ServiceMethod<T>的类,很有趣,似乎熟悉的又回来了,那么继承了该类,HttpServiceMethod就一定会实现invoke()这个抽象方法:

	@Overridefinal @Nullable ReturnT invoke(Object[] args) {Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);return adapt(call, args);//抽象方法}

这里是先生成了一个OkHttpCall(),(毕竟Retrofit底层还是okhttp)并且作为参数带入了adapt()方法

  • 这⾏代码负责将 ServiceMethod 解读到的信息(主要是⼀个 RequestFactory 、⼀个 OkHttpClient 和⼀个 ResponseConverter )封装进 OkHttpCall
  • ⽽这个对象可以在需要的时候(例如它的 enqueue() ⽅法被调⽤的时候), 利⽤ RequestFactoryOkHttpClient 来创建⼀个 okhttp3.Call 对象,并调⽤这个对象来发起⽹络请求,
  • 然后利⽤ ResponseConverter 对结果进⾏预处理之后,交回给 Retrofit 的 Callback 。

在实际操作中,异步请求是实际上交给了okhttp来做,在OkHttpCall类中,这里有异步请求的底层,通过parseResponse(rawResponse)转换成对象。

	call.enqueue(new okhttp3.Callback() {@Overridepublic void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) {Response<T> response;try {//将字节序列转换成对象response = parseResponse(rawResponse);} catch (Throwable e) {throwIfFatal(e);callFailure(e);return;}try {callback.onResponse(OkHttpCall.this, response);} catch (Throwable t) {throwIfFatal(t);t.printStackTrace(); // TODO this is not great}}

接着来看adapt(),adapt()意思就是适配,转换。它是一个抽象方法,

那我们只能继续回看到parseAnnotations这个方法:代码很长,我此处只显示真正的核心部分

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(///....省略n行if (!isKotlinSuspendFunction) {//核心部分,生成HttpServiceMethod并返回return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);} else ...
}

这里new了一个HttpServiceMethod的内部类CallAdapter(),最后还是会走到:

callAdapter.adapt(call);

这个⽅法会使⽤⼀个 CallAdapter 对象来把 OkHttpCall 对象进⾏转换,⽣成⼀个新的对象。

  • 默认情况下,返回的是⼀个 ExecutorCallbackCall ,它的作⽤是把操作切回主线程后再交给 Callback 。

另外,如果有⾃定义的 CallAdapter,这⾥也可以⽣成别的类型的对象,例如 RxJava 的 Observable

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/699217.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java】Java基础(实验一)

目录 一、实验目的 二、实验内容 三、实验小结 一、实验目的 掌握Java程序的编辑、调试与运行&#xff1b;了解Java引用类型&#xff0c;掌握数组的定义和引用。掌握Java基本数据类型和输入输出。掌握Java程序结构 二、实验内容 1.JDK的环境变量设置及测试。 &#xff08…

HarmonyOS Stage模型 应用配置文件讲解

好&#xff0c;上文 HarmonyOS Stage模型基本概念讲解 中&#xff0c;我们简单讲解了HarmonyOS 中 Stage模型的基本概念 那么 我们继续学习Stage模型的相关知识 上文之后 我们肯定对它的概念和基本结构 有了一个了解 那么 我们就来看一下 基于Stage模型 它里面一些基本的配置文…

InnoDB基础篇(4)-数据类型在InnoDB中的选择

在MySQL数据库中&#xff0c;数据类型在InnoDB存储引擎中的选择是非常重要的。选择合适的数据类型可以在存储空间、性能和数据完整性方面取得优化。在本篇博客中&#xff0c;我们将介绍一些常见的MySQL数据类型&#xff0c;并讨论它们在InnoDB中的选择。 整数类型&#xff08;I…

15-36V降压充电光伏MPPT充电方案

1.MPPT原理--简介 MPPT&#xff0c;全称为Maximum Power Point Tracking&#xff0c;即最大功点跟踪&#xff0c;它是一种通过调节电气模块的工作状态&#xff0c;使光伏板能够输出更多电能的电气系统能够将太阳能电池板发出的直流电有效地贮存在蓄电池中&#xff0c;可有效地…

C++内联函数的使用

C是一门强大而灵活的编程语言&#xff0c;提供了许多特性来优化代码性能。其中之一就是内联函数&#xff0c;它可以在编译时将函数调用的地方直接替换为函数体&#xff0c;从而减少函数调用的开销。在本文中&#xff0c;我们将深入探讨C内联函数的使用&#xff0c;重点介绍它的…

uniapp editor组件添加插入超链接

标题uniapp editor组件添加插入超链接 我基于官方editor组件开发了一个可添加超链接等其他功能的富文本插件 官方富文本编辑器editor组件改良扩展优化版

视频推拉流EasyDSS视频直播点播平台授权出现激活码无效并报错400是什么原因?

视频推拉流EasyDSS视频直播点播平台集视频直播、点播、转码、管理、录像、检索、时移回看等功能于一体&#xff0c;可提供音视频采集、视频推拉流、播放H.265编码视频、存储、分发等视频能力服务&#xff0c;在应用场景上&#xff0c;平台可以运用在互联网教育、在线课堂、游戏…

32.云原生Istio流量管理之官网Bookinfo应用实战演示

云原生专栏大纲 文章目录 流量管理基于版本的路由配置基于 Http header 的路由配置故障注入延迟故障注入异常故障注入故障注入测试 比例分配流量请求超时熔断什么是熔断创建 httpbin 服务创建访问者服务 流量管理 Istio 是服务治理的工具&#xff0c;Istio 的流量管理能力&am…

Django学习笔记-forms使用

1.创建forms.py文件,导入包 from django import forms from django.forms import fields from django.forms import widgets2. 创建EmployeeForm,继承forms.Form 3.创建testform.html文件 4.urls.py添加路由 5.views中导入forms 创建testform,编写代码 1).如果请求方式为GET,…

unity学习(38)——创建(create)角色脚本(panel)--EventSystem

1.在scripts文件夹下创建一个脚本CreatePlayerPanel.cs&#xff0c;脚本挂到panel上&#xff01;给panel加个tag&#xff0c;叫createPanel&#xff0c;脚本内容如下&#xff1a; using System.Collections; using System.Collections.Generic; using TMPro; using UnityEngin…

BM100 设计LRU缓存结构(java实现)

一、题目 设计LRU(最近最少使用)缓存结构&#xff0c;该结构在构造时确定大小&#xff0c;假设大小为 capacity &#xff0c;操作次数是 n &#xff0c;并有如下功能: Solution(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存get(key)&#xff1a;如果关键字 key …

Qt连续存图异常现象解决

我有一个图像采集软件&#xff0c;开始采集后&#xff0c;主线程会不断地接收到图像回调&#xff0c;然后每接收到一张图像数据&#xff0c;就通知业务线程保存该图像到本地文件。 但是实际运行的时候发现&#xff0c;可能是由于业务线程存图的操作占用资源&#xff0c;会导致…

Jenkins 使用指南:从安装到自动化部署!

Jenkins 使用指南&#xff1a;从安装到自动化部署&#xff01; Jenkins 是一个开源的持续集成和持续交付工具&#xff0c;用于自动化软件开发过程中的构建、测试和部署。本文将介绍 Jenkins 的配置和应用&#xff0c;包括如何安装 Jenkins、创建任务、配置构建触发器等。 1. …

【区块链】联盟链

区块链中的联盟链 写在最前面**FAQs** 联盟链&#xff1a;区块链技术的新兴力量**联盟链的定义****联盟链的技术架构**共识机制智能合约加密技术身份认证 **联盟链的特点**高效性安全性可控性隐私保护 **联盟链的应用场景****金融服务****供应链管理****身份验证****跨境支付**…

【行业交流】优积科技·国住人居与广东保利就学校、居住场景下模块化建筑技术的运用进行交流

近日&#xff0c;保利发展控股集团股份有限公司&#xff08;以下简称“保利发展”&#xff09;、 优积建筑科技发展(上海)有限公司&#xff08;以下简称“优积科技”&#xff09;、国住人居工程顾问有限公司&#xff08;以下简称“国住人居公司”&#xff09;就模块化建造体系与…

【信息系统项目管理师】--【信息技术发展】--【现代化创新发展】--【云计算】

文章目录 第二章 信息技术发展2.2 新一代信息技术及应用2.2.2 云计算1.技术基础2.关键技术3.应用和发展 第二章 信息技术发展 信息技术是在信息科学的基本原理和方法下&#xff0c;获取信息、处理信息、传输信息和使用信息的应用技术总称。从信息技术的发展过程来看&#xff0c…

变量长度之${#var}

1.${#var} ${#var}是用来计算变量$var的字符个数&#xff0c;即$var的字符串长度。对于var数组来说&#xff0c;${#var}表示的是数组中第一个元素的长度。 2.实例 2.1.统计字符串var的长度 样例&#xff1a; [rootkibana ~]# var123456789 [rootkibana ~]# echo ${#var} 9…

目标检测卷王YOLO卷出新高度:YOLOv9问世

论文摘要:如今的深度学习方法重点关注如何设计最合适的目标函数,使得模型的预测结果能够最接近真实情况。 同时,必须设计一个适当的架构,可以帮助获取足够的信息进行预测。 现有方法忽略了一个事实,即当输入数据经过逐层特征提取和空间变换时,大量信息将会丢失。 本文将深…

品牌如何从用户需求出发实现价值增长?

品牌想要实现长效发展的秘诀就在于不盲从市场&#xff0c;忠于消费者需求。从用户出发的品牌价值创新&#xff0c;往往有两种方式&#xff1a;一是满足用户需求&#xff0c;二是创造用户价值。今天媒介盒子就来和大家聊聊&#xff1a;如何从用户需求出发实现品牌价值增长。 一、…

C++的queue容器->基本概念、常用接口

#include<iostream> using namespace std; #include <queue> #include <string> //队列 queue class Person { public: Person(string name, int age) { this->m_Name name; this->m_Age age; } string m_Name; int…