Retrofit 的 Scala Adapter:用 Scala Future 类型化 HTTP 客户端
2026/9/19 0:35:43 网站建设 项目流程

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.Futurescala.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(); }

注册时有两个注意点:

  1. CallAdapter 工厂可以叠加注册。如果项目中同时使用多个适配器(如 Scala 与 Java 8CompletableFuture),Retrofit 会按注册顺序依次询问各工厂是否能处理当前返回类型;无法处理的工厂返回null,交给下一个。
  2. 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 响应Futureretrofit2.HttpException失败(携带 HTTP 状态码与错误信息)
网络错误Futurejava.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()
网络错误Futurejava.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):

  1. 若返回类型原始类型不是Future.class,返回null(不处理,交给下一个工厂);
  2. 若是裸Future(未参数化),抛出IllegalStateException"Future return type must be parameterized as Future<Foo> or Future<? extends Foo>"
  3. Future的第一个泛型参数上界作为内部类型;若内部类型不是Response,则走BodyCallAdapter
  4. 若内部类型是Response,要求其必须参数化(如Response<Foo>Response<? extends Foo>),否则抛出IllegalStateException
  5. 取出Response的泛型参数作为真正的响应体类型,构造ResponseCallAdapter

整个解析过程复用了CallAdapter.Factory提供的getRawTypegetParameterUpperBound工具方法(见 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-scalaPOM_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>泛型参数 TFuture<T>
ResponseCallAdapter<T>Response内的泛型参数 TFuture<Response<T>>

二者的共性实现思路是:

  1. 创建scala.concurrent.Promise
  2. 调用call.enqueue(...)发起异步请求,注册 RetrofitCallback
  3. onResponse/onFailure回调中把结果转交给Promisesuccess/failure);
  4. 返回promise.future()作为方法返回值。

由于请求走的是enqueue异步路径,调用服务方法不会阻塞调用线程Future的完成时机取决于网络请求结束的时机,后续可通过 Scala 的Future组合子(mapflatMaponComplete等)或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"的完整链路。


六、常见问题与注意事项

  1. 忘记参数化泛型:接口方法写成裸Future或裸Future<Response>会在构建服务时抛IllegalStateException,务必写成Future<User>Future<Response<User>>形式。
  2. 非 2XX 是否抛异常Future<T>会以HttpException失败;Future<Response<T>>则成功返回Response,必须自行判断isSuccessful()。两种语义不要混用。
  3. 网络异常始终是IOException:两种形态下,连接失败、超时等网络层错误都会让FutureIOException(或其子类)失败。
  4. 等待结果时注意超时:测试中使用Await.result(future, Duration.create(5, SECONDS))设置等待上限,生产代码同样应避免无限期阻塞等待。
  5. 与其它适配器共存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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询