Retrofit 原理深度解析

一个类型安全的 HTTP 客户端 — 核心是动态代理 + 注解解析 + OkHttp,把 HTTP 请求变成 Kotlin/Java 接口调用

Retrofit 动态代理 InvocationHandler Proxy.newProxyInstance 注解解析 OkHttp CallAdapter Converter 协程

目录导航(点击跳转)

一、Retrofit 是什么 — OkHttp 的上层封装

1 一句话定义
Retrofit是 OkHttp 的上层封装,牺牲部分灵活性换取类型安全声明式 API。它的核心价值是:把 HTTP 请求变成 Kotlin/Java 接口调用
2 Retrofit 与 OkHttp 的关系
维度OkHttpRetrofit
定位 通用 HTTP 客户端 类型安全的 HTTP 客户端(上层封装)
发起请求的方式 手动构造 RequestCallexecute() 定义接口方法 → 直接调用 → 自动发请求
响应处理 手动解析 ResponseBody 自动转换为声明类型的对象(通过 Converter)
底层引擎 自己就是引擎(拦截器、连接池、缓存) 底层复用 OkHttp(可通过 .client()传入自定义 OkHttpClient)
关键认知:OkHttp 能做的(拦截器、连接池、缓存策略),Retrofit 都能通过 .client(okHttpClient)传入自定义的 OkHttpClient 来实现。Retrofit 不替代 OkHttp,而是在 OkHttp 之上加了一层"接口化"的抽象
3 最基本的用法 — 感受"接口变请求"的魔法

用 Kotlin 写一个最简单的例子,感受 Retrofit 是怎么把接口方法变成 HTTP 请求的:

// 1. 定义接口 — 这就是你的 API 契约
interface GithubService {
    @GET("users/{user}/repos")
    fun listRepos(@Path("user") user: String): Call<List<Repo>>
}

// 2. 使用 — 接口方法调用自动变成 HTTP 请求
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val retrofit = Retrofit.Builder()
        .baseUrl("https://api.github.com/")        // 注意是 https
        .addConverterFactory(GsonConverterFactory.create())  // ← 必须添加!否则无法转换响应体
        .build()

    val service = retrofit.create(GithubService::class.java)
    val repos: Call<List<Repo>> = service.listRepos("octocat")
    //                                   ↑ 这一行触发了什么?→ 进入动态代理的 invoke()
}
魔法就发生在 retrofit.create(GithubService::class.java)——它返回的不是你写的 GithubService 实现类,而是一个运行时动态生成的代理对象。当你调用 service.listRepos("octocat")时,这个调用被代理拦截,转化为 HTTP 请求发出去。

二、两种使用方式 — Call 回调 vs suspend 协程

1 方式一:Call + 回调(传统异步)
// 接口定义 — 返回 Call<T>
interface ApiService {
    @GET("users/{user}/repos")
    fun listRepos(@Path("user") user: String): Call<List<Repo>>
}

// 使用 — 异步回调
val call = service.listRepos("octocat")
call.enqueue(object : Callback<List<Repo>> {  // 注意是 Callback,不是 CallBack
    override fun onResponse(call: Call<List<Repo>>, response: Response<List<Repo>>) {
        val data = response.body()
        println("Response: ${data?.get(0)?.name}")  // 注意空安全
    }

    override fun onFailure(call: Call<List<Repo>>, t: Throwable) {
        val error = t.message
    }
})

// 同步请求(不能在 Android 主线程!)
// val response = repos.execute()
注意:execute()是同步方法,会阻塞当前线程,不能在 Android 主线程使用,否则会抛 NetworkOnMainThreadException。一般只用 enqueue()做异步请求。
2 方式二:suspend + 协程(现代推荐)
// 接口定义 — 加上 suspend 关键字
interface ApiService {
    @GET("users/{user}/repos")
    suspend fun listRepos(@Path("user") user: String): Response<List<Repo>>
}

// 使用 — 在协程作用域中调用,写法像同步但实际是异步
viewModelScope.launch {
    try {
        val response = service.listRepos("octocat")
        val data = response.body()
    } catch (e: Exception) {
        val error = e.message
    }
}
协程方式的好处:
  • 代码像同步写法一样简洁,没有回调嵌套
  • 自动处理协程取消(ViewModel 销毁时自动取消请求)
  • 异常处理用 try-catch,比回调中的 onFailure更自然
3 两种方式对比

Call + 回调

  • 不依赖协程,兼容老项目
  • 手动控制取消:call.cancel()
  • 可以获取原始 Call 对象做更多操作

suspend + 协程

  • 代码更简洁,消除回调嵌套
  • 自动跟随协程生命周期
  • 异常处理更自然(try-catch)
  • 配合 Flow 可实现流式数据

三、源码解析 — create() 方法的三个关键步骤

1 create() 方法的完整源码

当你调用 retrofit.create(ApiService.class)时,Retrofit 内部做了三件事:验证接口 → 创建动态代理 → 在 invoke 中转发方法调用

public <T> T create(final Class<T> service) {
    // 步骤1:验证服务接口是否合法
    validateServiceInterface(service);

    // 步骤2:通过动态代理创建接口的实现对象
    return (T) Proxy.newProxyInstance(
        service.getClassLoader(),       // 类加载器
        new Class<?>[] { service },      // 要实现的接口数组(这里只有一个元素:service)
        new InvocationHandler() {       // 步骤3:所有接口方法调用都会进这个 invoke
            private final Object[] emptyArgs = new Object[0];

            @Override
            public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
                throws Throwable {
                // 如果调的是 Object 的方法(toString/equals/hashCode),直接走正常调用
                if (method.getDeclaringClass() == Object.class) {
                    return method.invoke(this, args);
                }
                args = args != null ? args : emptyArgs;
                Reflection reflection = Platform.reflection;
                // 如果是 default 方法,走 default 方法的调用逻辑
                // 否则:解析方法注解 → 构建 HTTP 请求 → 执行
                return reflection.isDefaultMethod(method)
                    ? reflection.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(service, method).invoke(proxy, args);
            }
        });
}
retrofit.create(ApiService.class)
validateServiceInterface()
验证接口合法性
Proxy.newProxyInstance()
运行时生成代理类
返回 $Proxy0 实例
(实现了 ApiService 的代理对象)
2 validateServiceInterface — 验证接口合法性
// 这里传入的 service 参数其实就是你调用 retrofit.create(ApiService::class.java) 传入的接口类
private void validateServiceInterface(Class<?> service) {
    // 验证1:必须是接口,不能是类或抽象类
    if (!service.isInterface()) {
        throw new IllegalArgumentException("API declarations must be interfaces.");
    }

    // 验证2:接口和它所继承的所有父接口都不能有泛型参数
    // 例如 interface Api<T> { ... } 是不允许的
    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());
    }

    // 如果开启了预加载(validateEagerly),则立即解析所有方法
    if (validateEagerly) {
        Reflection reflection = Platform.reflection;
        for (Method method : service.getDeclaredMethods()) {
            if (!reflection.isDefaultMethod(method)
                && !Modifier.isStatic(method.getModifiers())
                && !method.isSynthetic()) {
                loadServiceMethod(service, method);  // 预解析并缓存
            }
        }
    }
}

这个方法的职责很纯粹:

  • 必须是接口:Retrofit 基于 JDK 动态代理,而 JDK 代理只支持接口
  • 不支持泛型接口:因为泛型参数在运行时被擦除,Retrofit 无法得知具体类型
  • 递归检查父接口:确保你 extends 的父接口也符合要求
  • 可选预加载:validateEagerly = true时,在创建代理对象时就解析所有方法,方便及早发现注解错误
3 invoke 中的 Object 方法处理
// invoke 中的这段代码
if (method.getDeclaringClass() == Object.class) {
    return method.invoke(this, args);  // this = InvocationHandler 实例
}

这段代码的作用是:当调用代理对象的 Object 类方法(如 toString()equals()hashCode())时,不走 Retrofit 的网络请求逻辑,而是直接由 InvocationHandler 实例本身来处理这些方法调用。

为什么用 method.invoke(this, args)而不是 method.invoke(proxy, args)

因为 proxy是代理对象本身,如果你调用 proxy.toString(),它又会进入 invoke(),形成一个无限递归直到栈溢出。而 this指向的是 InvocationHandler 实例(一个普通对象),调用它的 toString()不会触发代理逻辑。

四、动态代理原理 — 为什么调用接口方法就能发请求?

1 先理解两个概念
概念一句话解释在 Retrofit 中的角色
代理(Proxy) 创建一个类,这个类实现了你给的接口,代理那些接口方法的实现 retrofit.create()返回的 service 对象就是代理
动态代理(Dynamic Proxy) 代理类在运行时动态生成,不是在编译时写好的 生成的代理类在 .java 源码中不存在,是 JVM 运行时生成的字节码
2 一次 service.listRepos() 调用的完整链路
1. 你的代码
service.listRepos("octocat")
2. 被 Proxy 拦截
进入 InvocationHandler.invoke()
3. 查询方法缓存
loadServiceMethod(service, method)
4. 解析注解
@GET → GET请求
@Path → 路径替换
5. 构建 OkHttp Request
URL + Method + Headers + Body
6. OkHttp 发起请求
HTTP GET https://api.github.com/users/octocat/repos
7. Converter 转换响应
ResponseBody → List<Repo>
8. CallAdapter 适配返回
Call<T> / suspend / Flow
9. 你的代码拿到结果
List<Repo> data
3 Retrofit 的三层架构
接口层 — 你用注解声明的 API 接口
interface ApiService { @GET("...") fun get(): Call<T> }
代理层 — 动态代理 + 注解解析(Retrofit 的核心)
Proxy.newProxyInstance()+ InvocationHandler+ ServiceMethod
网络层 — OkHttp 执行实际的 HTTP 请求
OkHttpClient、拦截器、连接池、缓存

每一层各司其职:

  • 接口层:你写接口 + 注解,声明"我要调什么 HTTP API"
  • 代理层:Retrofit 在运行时解析你的注解,把接口调用翻译成 OkHttp Request
  • 网络层:OkHttp 拿到 Request 后,执行真正的 Socket 通信

五、为什么需要 Proxy 对象?— 有代理 vs 无代理

1 一个自然的疑问

既然代理对象的每个方法都只是转发给 handler.invoke(),那为什么不直接调用 handler.invoke()呢?为什么还要绕一圈通过 Proxy.newProxyInstance()生成代理对象?

答案是:Proxy 对象不是可有可无的中间人,它承担着四个不可替代的关键角色。

2 代码对比 — 感受有代理和无代理的差异

没有 Proxy 的写法(每次都要手动反射):

// 调用方必须自己处理反射,丑陋且不安全
InvocationHandler handler = new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 解析注解、构建请求、执行...
        return executeHttpRequest(method, args);
    }
};

// 每次调用都要手动反射!
Method method = GithubService.class.getMethod("listRepos", String.class);
Object result = handler.invoke(null, method, new Object[]{"octocat"});
// 问题1: 没有类型安全 — 编译器不检查方法名、参数类型
// 问题2: 没有 IDE 自动补全 — 你只能手写方法名字符串
// 问题3: 必须显式处理反射异常 — try-catch 满天飞
// 问题4: 暴露了底层实现 — 调用方知道 InvocationHandler 的存在

有 Proxy 的写法(Retrofit 的实际做法):

// create() 返回的代理对象实现了 GithubService 接口
val service: GithubService = retrofit.create(GithubService::class.java)

// 调用方像调用普通对象一样自然
val repos: Call<List<Repo>> = service.listRepos("octocat")
// 优势1: 类型安全 — 编译器检查方法名和参数
// 优势2: IDE 自动补全 — 输入 service. 就能看到所有 API 方法
// 优势3: 完全透明 — 调用者不知道背后有 InvocationHandler
// 优势4: 可以传递给任何需要 GithubService 类型的地方
3 Proxy 的四个核心价值
价值说明没有 Proxy 的话
1 类型安全与接口一致性 代理对象实现了 GithubService接口,可以安全地传递给任何需要该类型的地方。编译器会检查方法名、参数类型。 调用方必须手动反射,方法名写错——编译期发现不了,运行时报错
2 隐藏底层转发细节 调用者完全无感知:只是调用了普通接口方法,不知道背后有 InvocationHandler在干活。 调用方必须显式构造 Method对象、处理反射异常,代码非常丑陋
3 标准设计模式 Proxy.newProxyInstance是 Java 官方提供的统一机制。InvocationHandler是"策略",代理对象是"外壳"——两者配合:外壳保证接口契约,策略提供具体实现。 每个框架都要自己发明一套"接口 → 实现"的机制,不一致、不好维护
4 多接口支持 一个代理对象可以同时实现多个接口(new Class[] { ApiA.class, ApiB.class } InvocationHandler只是一个普通对象,不具备多接口类型
一句话概括:Proxy 对象是动态代理的"外壳",它让 InvocationHandler这个通用处理器能够以特定接口的形态被调用,从而兼顾了灵活性与类型安全

InvocationHandler是"策略",代理对象是"外壳"——外壳保证接口契约,策略提供具体实现。

六、loadServiceMethod — 方法注解的缓存机制

1 为什么需要缓存?

同一个接口方法会被调用成千上万次(比如 listRepos()每次下拉刷新都会调),但方法的注解信息(@GET@Path@Query在编译后就不会变了。如果每次调用都重新解析一遍注解,就是白白浪费 CPU。

所以 Retrofit 用了一个 serviceMethodCache(本质是 ConcurrentHashMap),每个方法的解析结果只做一次,之后直接复用

2 loadServiceMethod 源码解读
ServiceMethod<?> loadServiceMethod(Class<?> service, Method method) {
    while (true) {
        // 第一步:先从缓存里查
        Object lookup = serviceMethodCache.get(method);

        // 情况A:缓存里已经是解析好的 ServiceMethod → 直接返回(最快路径)
        if (lookup instanceof ServiceMethod<?>) {
            return (ServiceMethod<?>) lookup;
        }

        // 情况B:缓存里啥也没有 → 当前线程负责解析
        if (lookup == null) {
            Object lock = new Object();
            synchronized (lock) {
                // putIfAbsent:塞一个"锁对象"占坑,告诉其他线程"我正在解析"
                lookup = serviceMethodCache.putIfAbsent(method, lock);
                if (lookup == null) {
                    // 成功占坑!开始解析注解
                    ServiceMethod<Object> result;
                    try {
                        result = ServiceMethod.parseAnnotations(this, service, method);
                    } catch (Throwable e) {
                        // 解析失败 → 把占坑的锁移除,让其他线程有机会重试
                        serviceMethodCache.remove(method);
                        throw e;
                    }
                    // 解析完成 → 把结果替换掉占坑的锁
                    serviceMethodCache.put(method, result);
                    return result;
                }
                // 如果 putIfAbsent 返回了别人的锁 → 走下面的等待逻辑
            }
        }

        // 情况C:缓存里是别人放的锁对象 → 说明另一个线程正在解析,等待它完成
        synchronized (lookup) {
            Object result = serviceMethodCache.get(method);
            if (result == null) {
                // 另一个线程解析失败了,当前线程重试
                continue;
            }
            return (ServiceMethod<?>) result;
        }
    }
}
核心思想:ConcurrentHashMap实现了一个无锁读 + 轻量级同步写的缓存。读操作完全无锁(get),只有写操作(putIfAbsent)需要同步,且同步粒度控制在单个方法级别(每个方法一把锁),不同方法之间不互相阻塞。
3 缓存状态的三种情况
serviceMethodCache.get(method)
情况A:返回 ServiceMethod
→ 已缓存,直接返回
情况B:返回 null
→ 当前线程插入锁占坑 → 解析 → 存入结果
情况C:返回别人的锁对象
→ 等待锁释放 → 获取别人解析好的结果

这个设计非常精巧:

  • 情况A是最常见的路径(热点路径),完全无锁,性能极高
  • 情况B只在第一次调用该方法时发生,同步粒度小(每个方法独立锁)
  • 情况C只在并发初始化时短暂出现,等待的线程不重复解析,直接复用别人的结果

七、核心思想总结

Retrofit 的本质:三句话

  1. Retrofit = 动态代理 + 注解解析 + OkHttp:这三个组件各司其职,代理负责拦截方法调用,注解解析负责翻译成 HTTP 请求,OkHttp 负责执行网络通信。
  2. retrofit.create() 返回的 service 对象是一个运行时生成的代理$Proxy0),它实现了你的接口,所有方法调用都被转发到 InvocationHandler.invoke()
  3. 方法的注解只解析一次,结果缓存复用loadServiceMethodConcurrentHashMap实现了无锁读 + 方法级同步写的缓存策略。

Retrofit 的核心组件分工

组件职责类比
动态代理 拦截接口方法调用,转发到 InvocationHandler 接线员 — 你拨一个号码(调方法),它帮你转接到正确的人
注解解析 把 @GET/@POST/@Path/@Query 翻译成 OkHttp Request 翻译官 — 把你的"声明式描述"翻译成底层的 HTTP 构造指令
OkHttp 执行实际的 Socket 通信(TCP 连接、TLS 握手、HTTP 请求) 快递员 — 真正把包裹送到目的地的人
Converter 请求体序列化 + 响应体反序列化(JSON ↔ 对象) 包装工 — 把物品装进包裹 / 把包裹拆开取物品
CallAdapter 把底层 Call 适配成目标返回类型(Call / suspend / Flow / Observable) 转接头 — 一个插头适配不同规格的插座

Retrofit 的优点

  • 类型安全:编译期检查 URL 模板、参数类型,避免运行时拼写错误
  • 声明式 API:接口 + 注解,意图清晰,代码简洁
  • 高度可扩展:可插拔的 Converter(Gson/Moshi/Protobuf)和 CallAdapter(RxJava/Coroutine)
  • 与 OkHttp 无缝集成:复用 OkHttp 的所有能力(拦截器、连接池、缓存)

代价与限制

  • 必须基于接口:JDK 动态代理的限制,不能代理类
  • 牺牲灵活性:不如直接使用 OkHttp 那样可以任意构造 Request
  • 注解表达能力有限:复杂的动态 URL、动态 Header 需要借助 @Url@HeaderMap等特殊注解
  • 性能开销:反射 + 动态代理有轻微开销(但实际项目中几乎可忽略)