ParameterHandler 是 Retrofit 中一个非常核心但又容易被忽视的组件。如果说动态代理是 Retrofit 的“大脑”,负责指挥整个流程,那么 ParameterHandler 就是“四肢”,负责将方法参数的每一个具体值,准确地填充到 HTTP 请求的对应位置。
下面我们从设计目的、类结构、创建过程和工作流程四个维度来详细剖析。
🎯 ParameterHandler 的设计目的
在 Retrofit 中,我们通过注解来描述接口方法:
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user, @Query("sort") String sort);
这里的 @Path("user") 和 @Query("sort") 就是参数的“标签”。Retrofit 需要知道:
- 对于
@Path("user"),当方法调用时传入的"square",应该替换 URL 路径中的{user}。 - 对于
@Query("sort"),传入的"updated"应该作为 URL 查询参数sort=updated。
ParameterHandler 的职责就是:接收一个具体的参数值,并把它正确地写入到即将构建的 RequestBuilder 中。每个被注解的参数都会对应一个 ParameterHandler 实例。
🏗️ 类结构:抽象与实现
ParameterHandler 是 Retrofit 中的一个抽象类,定义在 retrofit2.ParameterHandler 中。它的核心是一个泛型抽象方法:
abstract class ParameterHandler<T> {
abstract void apply(RequestBuilder builder, T value) throws IOException;
}
T:参数的类型(如String、Integer、List<String>等)。apply:将参数值value应用到RequestBuilder上。
对于每种注解类型,Retrofit 都有对应的具体实现类(都是静态内部类):
| 注解 | 对应 ParameterHandler 类 | 作用 |
|---|---|---|
@Path | Path | 替换 URL 路径中的占位符 |
@Query | Query | 添加查询参数 |
@QueryMap | QueryMap | 添加多个查询参数(Map) |
@Body | Body | 设置请求体 |
@Field | Field | 添加表单字段(用于 @FormUrlEncoded) |
@FieldMap | FieldMap | 添加多个表单字段 |
@Part | Part | 添加多部分请求的一部分 |
@PartMap | PartMap | 添加多个多部分部分 |
@Header | Header | 添加请求头 |
@HeaderMap | HeaderMap | 添加多个请求头 |
@Url | RelativeUrl | 动态设置 URL |
@QueryName | QueryName | 添加无值的查询参数(如 ?debug) |
除了这些,还有一些辅助类用于处理集合、数组和可空性,比如 ParameterHandler.IterableParameter 会包装另一个 ParameterHandler 来处理 List 类型。
🔨 创建过程:ServiceMethod 解析参数
ParameterHandler 是在构建 ServiceMethod 时创建的。这个过程发生在 ServiceMethod.Builder 的 build() 方法中,它遍历方法的所有参数,并为每个参数调用 parseParameter()。
简化后的逻辑大致如下:
private ParameterHandler<?> parseParameter(int index, Type type, Annotation[] annotations) {
ParameterHandler<?> result = null;
// 遍历该参数上的所有注解
for (Annotation annotation : annotations) {
ParameterHandler<?> handler = parseParameterAnnotation(index, type, annotations, annotation);
if (handler == null) continue;
if (result != null) {
throw methodError("Multiple Retrofit annotations found, only one allowed.");
}
result = handler;
}
if (result == null) {
throw methodError("No Retrofit annotation found.");
}
return result;
}
核心在 parseParameterAnnotation 中,它根据注解类型创建对应的 ParameterHandler。例如:
- 如果注解是
@Path,它会检查该参数类型是否可以被 URL 编码,然后返回一个ParameterHandler.Path。 - 如果注解是
@Query,它会根据参数的实际类型(原始类型、可空类型、集合、数组等)决定返回哪种ParameterHandler。例如,如果参数是List<String>,它会创建一个ParameterHandler.Query,并包装一个用于迭代的ParameterHandler.IterableParameter。
在创建 ParameterHandler 时,还会将注解中指定的名称(如 @Path("user") 中的 "user")和相关的转换器(如 Converter)传入。对于像 @Body 这样的注解,它会持有请求体转换器(Converter<T, RequestBody>)。
最终,所有参数的 ParameterHandler 被收集到一个数组中,保存在 ServiceMethod 的 parameterHandlers 字段中。
⚙️ 工作流程:从参数到 RequestBuilder
当我们在运行时调用接口方法(如 listRepos("square", "updated"))时,动态代理会触发 InvocationHandler.invoke,然后调用 loadServiceMethod(method) 获取或创建 ServiceMethod,并用传入的参数数组 args 创建一个 OkHttpCall。
在 OkHttpCall 内部,当需要真正构建 okhttp3.Request 时,会调用 ServiceMethod.toRequest(args)。这个方法的核心逻辑就是:
Request toRequest(Object... args) throws IOException {
RequestBuilder requestBuilder = new RequestBuilder(...);
// 参数处理
ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
int argumentCount = args != null ? args.length : 0;
if (argumentCount != handlers.length) {
throw new IllegalArgumentException("Argument count mismatch.");
}
for (int i = 0; i < argumentCount; i++) {
handlers[i].apply(requestBuilder, args[i]);
}
return requestBuilder.build();
}
这里就是 ParameterHandler 施展魔法的地方:遍历每个参数的 handler,调用其 apply 方法,将参数值写入 RequestBuilder。
🔍 典型实现分析
我们挑几个典型的实现来看看它们是如何工作的。
1. Path
static final class Path<T> extends ParameterHandler<T> {
private final String name;
private final Converter<T, String> valueConverter;
@Override
void apply(RequestBuilder builder, T value) throws IOException {
if (value == null) {
throw new IllegalArgumentException("Path parameter \"" + name + "\" must not be null.");
}
// 将参数值转换为字符串(可能经过编码)
String stringValue = valueConverter.convert(value);
builder.addPathParam(name, stringValue);
}
}
builder.addPathParam 内部会用 stringValue 替换 URL 模板中的 {name} 占位符。
2. Query
static final class Query<T> extends ParameterHandler<T> {
private final String name;
private final Converter<T, String> valueConverter;
private final boolean encoded;
@Override
void apply(RequestBuilder builder, T value) throws IOException {
if (value == null) return; // 忽略 null 值
String stringValue = valueConverter.convert(value);
builder.addQueryParam(name, stringValue, encoded);
}
}
对于集合类型,Retrofit 会使用 ParameterHandler.IterableParameter 包装 Query,在 apply 时迭代调用 Query.apply 多次,从而生成 ?name=value1&name=value2。
3. Body
static final class Body<T> extends ParameterHandler<T> {
private final Converter<T, RequestBody> converter;
@Override
void apply(RequestBuilder builder, T value) {
if (value == null) {
throw new IllegalArgumentException("Body parameter value must not be null.");
}
RequestBody body;
try {
body = converter.convert(value);
} catch (IOException e) {
throw new RuntimeException("Unable to convert " + value + " to RequestBody", e);
}
builder.setBody(body);
}
}
它直接使用配置的 Converter 将对象转换为 RequestBody,然后设置给 RequestBuilder。
4. Header
static final class Header<T> extends ParameterHandler<T> {
private final String name;
private final Converter<T, String> valueConverter;
@Override
void apply(RequestBuilder builder, T value) throws IOException {
if (value == null) return;
String stringValue = valueConverter.convert(value);
builder.addHeader(name, stringValue);
}
}
类似地,它把参数值作为请求头的值添加进去。
🧩 处理集合与可空性的技巧
Retrofit 为了支持集合参数(如 @Query List<String>),巧妙地使用了组合模式。它定义了一个 ParameterHandler.IterableParameter 类:
static final class IterableParameter<T> extends ParameterHandler<Iterable<T>> {
private final ParameterHandler<T> handler;
@Override
void apply(RequestBuilder builder, Iterable<T> values) throws IOException {
if (values == null) return;
for (T value : values) {
handler.apply(builder, value);
}
}
}
当检测到参数是 Iterable 类型时,parseParameterAnnotation 会递归地为元素类型创建一个 ParameterHandler(比如 Query<String>),然后用 IterableParameter 包装它。这样,在调用 apply 时就会循环处理每个元素。
对于数组,也有类似的 ArrayParameterHandler。
对于可空性,大多数 handler 都会检查 value == null 然后直接返回(不抛出异常),除非像 @Path 那样必须非空。
📦 与 RequestBuilder 的交互
RequestBuilder 是 OkHttp 的 Request 的建造器,它内部封装了 okhttp3.Request.Builder,并提供了各种添加参数的方法,例如:
addPathParam(name, value)addQueryParam(name, value, encoded)addHeader(name, value)setBody(RequestBody)addFormField(name, value)addPart(Headers headers, RequestBody body)
每个 ParameterHandler 最终都会调用这些方法来修改请求。
💡 总结
ParameterHandler 是 Retrofit 将声明式接口转换为实际 HTTP 请求的“施工队”。它的设计体现了几个优秀的软件工程原则:
- 单一职责:每个 handler 只负责处理一种注解,逻辑清晰。
- 组合优于继承:通过
IterableParameter等包装类,优雅地支持了集合和数组。 - 延迟绑定:handler 在构建
ServiceMethod时创建,与实际参数值分离,保证了线程安全。 - 可扩展性:如果要支持新的注解类型,只需要新增一个
ParameterHandler子类,并在parseParameterAnnotation中添加分支即可(虽然不开放给开发者,但框架内部可以轻易扩展)。
理解 ParameterHandler,能让你更清楚地知道当你在 Retrofit 接口中写一个 @Query Map<String, String> 时,背后发生了怎样的精妙转换。