Retrofit 的 Scala Adapter:用 Scala Future 类型化 HTTP 客户端
【免费下载链接】retrofitA type-safe HTTP client for Android and the JVM项目地址: https://gitcode.com/gh_mirrors/re/retrofit
导读
本文讲解 Retrofit 官方适配器adapter-scala的完整用法与底层原理。通过注册ScalaCallAdapterFactory,你可以让 Retrofit 服务接口直接返回scala.concurrent.Future,把异步 HTTP 请求无缝接入 Scala 的Future/Promise生态。读完本文,你将掌握该适配器的两种返回类型配置(Future<T>与Future<Response<T>>)、Maven/Gradle 依赖引入方式、错误语义差异,以及它内部的CallAdapter适配机制与测试验证方式。
一、什么是 Scala Adapter
retrofit-adapters/scala模块是一个标准的 RetrofitCallAdapter适配器,其唯一职责是:把 Retrofit 的Call<T>转换为 Scala 的Future。模块的 POM 描述(见 retrofit-adapters/scala/gradle.properties)将其定位为 "A Retrofit CallAdapter for Scala's Future"。
在默认情况下,Retrofit 服务接口方法只能返回Call<T>(或内置支持的其它类型)。而ScalaCallAdapterFactory允许你写出这样的接口:
interface MyService { @GET("/user") Future<User> getUser(); }请求发出后立即返回一个Future,无需手动维护Callback,也无需依赖 RxJava 等其它异步库。
适用前提:该模块基于
scala.concurrent.Future与scala.concurrent.Promise实现,需要项目引入 Scala 标准库(scala-library)。从源码结构看,它适用于同时使用 Retrofit 与 Scala 的 JVM 项目(如 Scala/JVM 服务端或混合语言工程)。
二、快速上手:注册 CallAdapter 工厂
在构建Retrofit实例时,通过addCallAdapterFactory注册ScalaCallAdapterFactory即可(对应 retrofit-adapters/scala/README.md 中的用法):
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://example.com/") .addCallAdapterFactory(ScalaCallAdapterFactory.create()) .build();工厂采用无状态单例工厂设计,create()是唯一的公开入口,构造函数为私有(见 ScalaCallAdapterFactory.java):
public static ScalaCallAdapterFactory create() { return new ScalaCallAdapterFactory(); } private ScalaCallAdapterFactory() {}随后,服务接口的返回类型就可以使用Future:
interface MyService { @GET("/user") Future<User> getUser(); }注册时有两个注意点:
- CallAdapter 工厂可以叠加注册。如果项目中同时使用多个适配器(如 Scala 与 Java 8
CompletableFuture),Retrofit 会按注册顺序依次询问各工厂是否能处理当前返回类型;无法处理的工厂返回null,交给下一个。 - Converter 工厂仍按需注册。
ScalaCallAdapterFactory只负责把Call<T>变成Future,响应体到User的反序列化仍然由Converter.Factory(如 Gson、Moshi)完成,二者职责正交。
三、两种返回类型配置
ScalaCallAdapterFactory支持Future泛型参数的两种形态(源码 Javadoc 与get方法明确说明,见 ScalaCallAdapterFactory.java):
1. 直接返回响应体:Future<T>
@GET("/user") Future<User> getUser();语义如下(对应 BodyCallAdapter.java 的实现):
| 场景 | 行为 |
|---|---|
| 2XX 成功响应 | Future成功完成,值为反序列化后的响应体(response.body()) |
| 非 2XX 响应 | Future以retrofit2.HttpException失败(携带 HTTP 状态码与错误信息) |
| 网络错误 | Future以java.io.IOException失败 |
核心实现使用Promise桥接 Retrofit 的异步回调:
@Override public Future<T> adapt(Call<T> call) { Promise<T> promise = Promise.apply(); call.enqueue(new Callback<T>() { @Override public void onResponse(Call<T> call, Response<T> response) { if (response.isSuccessful()) { promise.success(response.body()); } else { promise.failure(new HttpException(response)); } } @Override public void onFailure(Call<T> call, Throwable t) { promise.failure(t); } }); return promise.future(); }2. 返回 Response 包装:Future<Response<T>>
@GET("/user") Future<Response<User>> getUserResponse();语义如下(对应 ResponseCallAdapter.java 的实现):
| 场景 | 行为 |
|---|---|
| 任意 HTTP 响应(含 4XX/5XX) | Future成功完成,值为完整的Response<T>对象(通过response.isSuccessful()判断业务是否成功,可读取errorBody()) |
| 网络错误 | Future以java.io.IOException失败 |
@Override public Future<Response<T>> adapt(Call<T> call) { Promise<Response<T>> promise = Promise.apply(); call.enqueue(new Callback<T>() { @Override public void onResponse(Call<T> call, Response<T> response) { promise.success(response); // 不区分状态码,全部成功返回 } @Override public void onFailure(Call<T> call, Throwable t) { promise.failure(t); } }); return promise.future(); }两者如何选择?需要区分"业务成功/失败"(直接看Future成功与否)时选Future<T>;需要拿到状态码、响应头或错误响应体时选Future<Response<T>>。注意Future<Response<T>>形态下Future几乎总是成功完成,HTTP 错误不会抛异常,需要自己检查Response.isSuccessful()。
工厂的类型判定逻辑
ScalaCallAdapterFactory.get()的完整判定流程(见 ScalaCallAdapterFactory.java):
- 若返回类型原始类型不是
Future.class,返回null(不处理,交给下一个工厂); - 若是裸
Future(未参数化),抛出IllegalStateException:"Future return type must be parameterized as Future<Foo> or Future<? extends Foo>"; - 取
Future的第一个泛型参数上界作为内部类型;若内部类型不是Response,则走BodyCallAdapter; - 若内部类型是
Response,要求其必须参数化(如Response<Foo>或Response<? extends Foo>),否则抛出IllegalStateException; - 取出
Response的泛型参数作为真正的响应体类型,构造ResponseCallAdapter。
整个解析过程复用了CallAdapter.Factory提供的getRawType与getParameterUpperBound工具方法(见 CallAdapter.java),因此通配符泛型同样被支持,例如Future<? extends User>、Future<Response<? extends User>>都能正确解析出响应体类型。
四、依赖引入(Maven / Gradle)
adapter-scala的坐标如下(对应 retrofit-adapters/scala/README.md 中的 Download 章节):
Maven:
<dependency> <groupId>com.squareup.retrofit2</groupId> <artifactId>adapter-scala</artifactId> <version>latest.version</version> </dependency>Gradle:
implementation 'com.squareup.retrofit2:adapter-scala:latest.version'其中latest.version应替换为实际使用的 Retrofit 版本(建议与核心retrofit模块版本保持一致)。gradle.properties中确认了该模块的 Maven 坐标信息:POM_ARTIFACT_ID=adapter-scala、POM_NAME=Adapter: Scala。
另外两点版本提示:
- 仓库通过 gradle/libs.versions.toml 统一管理各模块版本,实际发布时
adapter-scala与核心 retrofit 同步发版; - 开发版快照(snapshot)可以从 Sonatype 的
snapshots仓库获取,用于尝鲜未发布的开发版本。
五、原理剖析:Future 是如何产生的
Retrofit 的整套扩展机制建立在CallAdapter之上。CallAdapter<R, T>的核心契约(见 CallAdapter.java):
responseType():返回适配器转换 HTTP 响应体时使用的值类型(如Future<User>对应User),Retrofit 据此准备 Converter;adapt(Call<R> call):把 Retrofit 的Call包装成目标类型T(这里是Future)。
adapter-scala的两个内部适配器完整落实了该契约:
| 适配器 | responseType() | adapt产物 |
|---|---|---|
BodyCallAdapter<T> | 泛型参数 T | Future<T> |
ResponseCallAdapter<T> | Response内的泛型参数 T | Future<Response<T>> |
二者的共性实现思路是:
- 创建
scala.concurrent.Promise; - 调用
call.enqueue(...)发起异步请求,注册 RetrofitCallback; - 在
onResponse/onFailure回调中把结果转交给Promise(success/failure); - 返回
promise.future()作为方法返回值。
由于请求走的是enqueue异步路径,调用服务方法不会阻塞调用线程;Future的完成时机取决于网络请求结束的时机,后续可通过 Scala 的Future组合子(map、flatMap、onComplete等)或Await.result等待结果。
测试如何验证
仓库提供了两组针对性的测试,是理解该适配器行为的最佳佐证:
- FutureTest.java:基于
MockWebServer模拟真实 HTTP 交互,逐一验证 6 种场景——Future<String>的 200 成功(Await.result拿到"Hi")、404 抛HttpException、断连抛IOException,以及Future<Response<String>>的 200 / 404(可读取errorBody())/ 断连三种行为; - ScalaCallAdapterFactoryTest.java:验证工厂的类型解析——
Future<String>、Future<? extends String>、Future<List<String>>、Future<Response<String>>等形态的responseType()解析结果,以及裸Future、裸Future<Response>抛出对应IllegalStateException的行为。
此外测试还表明,使用该适配器时若同时配置一个简单的Converter.Factory(如测试中的 StringConverterFactory.java,把ResponseBody直接转成String),即可跑通"发起请求 → 反序列化 → 填充 Future"的完整链路。
六、常见问题与注意事项
- 忘记参数化泛型:接口方法写成裸
Future或裸Future<Response>会在构建服务时抛IllegalStateException,务必写成Future<User>、Future<Response<User>>形式。 - 非 2XX 是否抛异常:
Future<T>会以HttpException失败;Future<Response<T>>则成功返回Response,必须自行判断isSuccessful()。两种语义不要混用。 - 网络异常始终是
IOException:两种形态下,连接失败、超时等网络层错误都会让Future以IOException(或其子类)失败。 - 等待结果时注意超时:测试中使用
Await.result(future, Duration.create(5, SECONDS))设置等待上限,生产代码同样应避免无限期阻塞等待。 - 与其它适配器共存:
ScalaCallAdapterFactory只认Future返回类型,其它类型会返回null交给后续工厂,可放心与CompletableFuture、RxJava 等适配器一同注册。
七、小结
retrofit-adapters/scala是 Retrofit 官方为 Scala 生态提供的轻量异步适配器:通过一个工厂类加两个内部CallAdapter,用约一百行核心代码完整支撑了Future<T>与Future<Response<T>>两种异步编程形态。它把 Retrofit 的Call回调模型桥接到 Scala 的Promise/Future模型上,让 Scala 开发者可以用惯用的组合子风格处理 HTTP 请求,同时完整保留 Retrofit 的类型安全接口声明与 Converter 反序列化体系。
想进一步深入,可以阅读 ScalaCallAdapterFactory.java 的类型判定源码,或运行 FutureTest.java 观察两种形态在成功、4XX、网络异常三种场景下的真实行为。
【免费下载链接】retrofitA type-safe HTTP client for Android and the JVM项目地址: https://gitcode.com/gh_mirrors/re/retrofit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考