Retrofit源码解析:从动态代理到适配器与建造者模式

发布时间:2026/10/13 5:23:51
Retrofit源码解析:从动态代理到适配器与建造者模式 聊到 Retrofit 的源码解析我已经在前面写了四篇从基础用法、源码调用链梳理、注解机制分析到请求执行的完整流程。基本上“一个接口方法是如何变成一次真实网络请求”的脉络已经清楚了。这第五篇我想换个角度聊把注意力从“流程”转向“结构”系统拆一拆 Retrofit 这个开源网络库在设计层面用到的那些设计模式。其实刚接触源码那阵子我也抱着“找模式”的心态去一行行扫代码结果越扫越乱代理、适配器、工厂、建造者……每个名词都认识但就是串不起来。后来才意识到设计模式在 Retrofit 里从来不是硬贴上去的装饰品而是每一个核心机制自然长出来的产物。动态代理解决了“接口即API”的开发体验适配器解决了返回类型不可控的问题转换器解决了数据格式和核心框架的耦合Builder 则是配置项爆炸后的必然选择。这篇文章适合这样几类人读已经把 Retrofit 用得挺熟、但一直没想明白它内部长什么样的朋友读过前面几篇、想更进一步理解代码结构的同学以及正打算在业务代码里“偷师”一些设计思路的开发者。我会直接从源码中出现的模式切入一边拆代码一边讲清楚“为什么偏偏是它”。1. Retrofit 中的设计模式全景先看见森林再研究树木1.1 一句话看懂 Retrofit 的工作方式Retrofit 做的事情用一句话讲就是把你声明的一个 Java 接口翻译成对底层网络请求库的调用再把返回结果按你想要的形态交给你。这里有两层翻译。第一层是“从接口方法到请求描述”这一层靠的是动态代理加注解解析第二层是“从底层请求对象到上层返回类型”这一层靠的是适配器CallAdapter和转换器Converter。而把这些零件全部组装起来的骨架则大量依赖 Builder、工厂方法这类创建型模式来降低零件之间的耦合。所以你会发现一个很有意思的现象Retrofit 里几乎没有一个设计模式是孤立的。动态代理在工作时需要调用 ServiceMethod 的解析逻辑而 ServiceMethod 里面躺着 CallAdapter 和 ConverterBuilder 建造出来的 Retrofit 对象又把这些 Factory 通过平台单例统一管理。模式之间互相配合才形成了这个库完整的扩展能力。1.2 模式清单与职责对照表先把整篇文章要讲的内容摆到桌面上这样后续分析起来有明确坐标。设计模式出现位置解决的核心问题动态代理create()方法让调用方只写接口就能触发请求避免为每个接口生成手写实现类建造者模式Retrofit.Builder参数多、可选参数多防止构造函数难以阅读和扩展外观模式Retrofit 对象本身对上层屏蔽 OkHttp 的线程调度、连接池、重试等细节适配器模式CallAdapter把统一的Call对象适配成 RxJava、协程等不同返回形态策略模式Converter序列化/反序列化算法可替换比如 Gson 换 Moshi工厂模式CallAdapter.Factory、Converter.Factory解耦适配器与转换器的创建过程让用户自由插入扩展单例模式Platform.get()避免重复检测运行平台全局共享平台相关配置这张表是这篇文章的地图。下面几节我会把前面几项单独剖开并把容易混淆的点专门拿出来对比。1.3 看源码时的一个心态建议读设计模式最忌讳的是“为了找模式而找模式”。你真的去翻 Retrofit 源码会发现很多地方它只是长得像某种模式或者只用了模式的一部分。比如有人会说 Retrofit 里也用到了装饰器模式因为某些 Converter 会额外包装一层但 Retrofit 本身并没有刻意去实现装饰器。这种地方不需要硬扣。带着“这个类为什么长这样”的问题去读比带着“这是什么模式”的问题去读要有效得多。模式是结果不是原因。2. 动态代理模式Retrofit 的发动机藏在一行代码里2.1 create() 方法是怎么把接口变成请求的动态代理是 Retrofit 整个库最核心的一环也是最容易让新手读源码时卡住的地方。核心代码其实只有一段public T T create(final ClassT service) { // 1. 校验传入的 class 必须是一个接口 validateServiceInterface(service); // 2. 通过 JDK 动态代理生成接口的代理对象 return (T) Proxy.newProxyInstance( service.getClassLoader(), new Class?[]{service}, new InvocationHandler() { private final Platform platform Platform.get(); Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 代理 Object 类的方法时直接转发避免死循环 if (method.getDeclaringClass() Object.class) { return method.invoke(this, args); } // 这是最关键的调用入口把接口方法转换成请求 ServiceMethodObject, Object serviceMethod loadServiceMethod(method); OkHttpCallObject, Object okHttpCall new OkHttpCall(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } }); }第一次看这段代码的人通常会疑惑接口没有实现类Proxy.newProxyInstance到底生成了什么可以把它理解成一个“转发器”。当我们调用api.getUser(123)时因为api是代理对象这个调用不会进入任何真实实现类而是被InvocationHandler.invoke截获。method就是getUser这个方法对象args就是123这个参数。接下来loadServiceMethod会把方法的注解、参数注解全部解析成一份“请求说明书”然后交给OkHttpCall去真正执行。这里有一个很关键的细节validateServiceInterface里会检查这个接口是否继承了其他接口如果有额外的接口继承会直接抛出异常。原因是 Retrofit 希望接口声明足够“干净”这样解析注解时不会因为继承链而产生歧义。2.2 代理流程拆解从方法调用到 Call 对象动态代理之后完整的调用链是这样的调用方执行代理对象上的方法。invoke里根据method从缓存中取ServiceMethod第一次调用时创建并解析注解、参数、返回类型。用ServiceMethod和实际参数args构建OkHttpCall。通过CallAdapter.adapt()把OkHttpCall包装成调用方期望的返回类型。如果是同步请求直接返回结果如果是异步请求返回一个带回调的包装对象。我在前几篇里详细拆过ServiceMethod的构造过程这里只强调一个点ServiceMethod是线程安全的创建之后可以被多个请求共享而OkHttpCall是每次调用都新建的因为它包含一次请求的完整参数状态。动态代理在这里的价值就是让接口方法到请求对象之间的映射自动化了。2.3 如果不用动态代理会怎么样理解了动态代理的价值最好的方式是做一个反事实实验如果 Retrofit 不用动态代理开发者需要做什么常规做法是为每个 API 接口写一个实现类每个方法里手动解析注解、拼接 URL、构建请求。这样有两个致命问题一是一百个接口就要维护一百个模板代码几乎一样的类代码量爆炸二是新加一个接口时很容易漏掉某些注解处理逻辑出问题只能靠运行时慢慢找。动态代理把“接口定义”和“逻辑实现”彻底分开。有接口就有代理方法的数量不影响核心框架的设计开发者体验也保持在“只写声明”这个极简状态。这种做法的另一层好处是接口成了模块之间的契约。调用方看到的永远是接口不关心底层实现而底层实现也可以在不改接口的前提下替换扩展性非常强。3. 构建者模式与外观模式把复杂配置和底层网络栈都“藏”起来3.1 Retrofit.Builder 是如何收拢参数的Retrofit 的构造方式在开源库中极具代表性人人都写过这样一段代码Retrofit retrofit new Retrofit.Builder() .baseUrl(https://api.example.com/) .client(new OkHttpClient()) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build();Builder的存在看起来顺理成章但它并不是单纯为了“链式调用好看”。让我们看看build()方法里做了什么public Retrofit build() { if (baseUrl null) { throw new IllegalStateException(Base URL required.); } okhttp3.Call.Factory callFactory this.callFactory; if (callFactory null) { callFactory new OkHttpClient(); } Executor callbackExecutor this.callbackExecutor; if (callbackExecutor null) { callbackExecutor platform.defaultCallbackExecutor(); } ListCallAdapter.Factory callAdapterFactories new ArrayList(this.callAdapterFactories); callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor)); ListConverter.Factory converterFactories new ArrayList(1 this.converterFactories.size() platform.defaultConverterFactoriesSize()); // 补充默认转换工厂 converterFactories.add(new BuiltInConverters()); converterFactories.addAll(this.converterFactories); converterFactories.addAll(platform.defaultConverterFactories()); return new Retrofit(...); }这里透露出 Builder 的真正价值它负责把“用户可能遗漏的配置”用默认值补齐把必填项在构建时立刻检查而不是等到第一次发请求时才报错。baseUrl必填、callFactory默认新建OkHttpClient、callbackExecutor默认用主线程执行器这些规则全部集中在build()里既好读又好维护。3.2 为什么配置类几乎都选择 Builder如果你写过一个参数特别多的类一定遇到过这样的痛苦构造函数维护六七个参数每个参数可能为 null调用方根本分不清哪个是必填哪个是选填。用重载组合三四个参数已经能整出一堆重载方法扩展到十个参数时基本没法看。Builder 模式解决的不只是“参数很多”而是“有些参数必填、有些参数选填、某些参数之间还有隐含依赖”的问题。比如 Retrofit 里如果你不指定callFactory它会默认创建OkHttpClient如果你指定了它就不会重复创建。这种逻辑放在构造函数里会非常笨重放在 Builder 里就非常自然。另外一个值得学习的地方是 Builder 的校验时机。很多团队写 Builder 只把它当“setter 的链式写法”真正的校验却放到使用阶段才做这样其实没有享受 Builder 的全部好处。Retrofit 的做法是在build()阶段把所有必填项与默认值处理完构造出来的对象一定是完整可用的这种思路可以直接借鉴到业务配置类中。3.3 外观模式Retrofit 作为门面替你挡住了 OkHttp说完 Builder再往深一层看我们拿着构建好的 Retrofit 对象发请求时其实是在和一个“门面”打交道。Retrofit 对外暴露的接口非常简洁create()、newBuilder()以及内部的callFactory、callbackExecutor、baseUrl等但背后真正干活的是 OkHttp。这里就是典型的外观模式上层调用者不需要关心 OkHttp 的连接池怎么管理、线程池怎么调度、重试拦截器怎么挂载只需要说一句“我要请求这个接口返回这个类型”剩下的复杂度全部被 Retrofit 挡住。外观模式和代理模式在表面上有点像但意图完全不同。代理模式控制访问、增加额外逻辑外观模式统一简化入口。Retrofit 不是 OkHttp 的代理而是 OkHttp 的“前台接待员”。我见过不少初学者把这一点搞混等到下一节我会专门把这类容易混淆的点挑出来聊清楚。4. 适配器模式与工厂模式让返回类型和序列化方案彻底解耦4.1 CallAdapter 到底在适配什么看 Retrofit 源码时CallAdapter可能是除了动态代理之外第二个值得停下来细品的接口。它的定义非常短public interface CallAdapterR, T { Type responseType(); T adapt(CallR call); }responseType()返回的是服务器响应体解析后的目标类型比如Useradapt()接收一个CallR返回一个期望类型T。这玩意的核心价值在于Retrofit 的核心请求流程只需要维护一个Call对象至于这个Call最终是变成同步返回值、RxJava 的Observable、协程的挂起函数还是 Guava 的ListenableFuture全部交给CallAdapter去适配。举一个最直观的例子。默认情况下你写接口方法返回CallUserRetrofit 的DefaultCallAdapterFactory会直接把原Call返回如果你引入 RxJava 并配置了RxJava2CallAdapterFactory方法返回类型可以写成ObservableUser这时候底层OkHttpCall会被包装成Observable。上层调用方式完全不同但请求执行、参数解析、返回数据反序列化这部分逻辑一行都不用改。这带来的好处是巨大的第三方可以针对自己的异步体系写适配器而不需要改动 Retrofit 本身。4.2 Converter策略模式的一场优雅实践Converter和CallAdapter经常被搞混但它俩解决的问题完全不在一个维度。看接口定义更清楚public interface ConverterF, T { T convert(F value) throws IOException; abstract class Factory { public abstract ConverterF, T responseBodyConverter( Type type, Annotation[] annotations, Retrofit retrofit); public abstract ConverterF, T requestBodyConverter( Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit); } }Converter管的是“怎么把 HTTP 的请求体、响应体转换成 Java 对象”。用 Gson 就用GsonConverterFactory用 Moshi 就换MoshiConverterFactory用 Protobuf 就再换一个。核心框架不关心你用哪种 JSON 库只要实现框架规定的Converter接口就行。这其实就是策略模式定义一组算法序列化/反序列化每种算法封装成独立的类运行时动态切换。Retrofit 在构建时把用户传入的所有Converter.Factory串成一个列表当需要解析某个类型时依次询问每个工厂“能不能处理这个类型”第一个能处理的工厂负责产出对应的Converter。这种设计的妙处在于数据格式不再与代码逻辑耦合。同一个Retrofit对象只换一个 Converter 工厂就能让整个 App 的数据协议从 JSON 切到 XML代价非常小。4.3 Factory 是工厂模式的骨架不管是CallAdapter.Factory还是Converter.Factory背后都有一套工厂模式的骨架在支撑。Factory本身不直接创建用户要的那个对象而是定义了一个统一入口让各个实现类去决定具体创建什么。以CallAdapter.Factory为例它可以拿到接口方法的返回类型returnType、方法上的注解annotations、以及当前的Retrofit实例。有了这些信息不同的Factory就能做不同的判断RxJava 的工厂看到返回类型是ObservableT就返回对应的适配器协程的工厂看到最后一个参数是Continuation就创建挂起函数的适配器谁都不认识这个返回类型就返回 null让列表里的下一个工厂去尝试。这种“责任链工厂方法”的组合本质上是一个高度可扩展的插槽机制。用户代码可以通过addCallAdapterFactory往链上挂自定义工厂从而实现任意返回类型这个过程完全没有侵入核心代码。到这里可以提炼一句Retrofit 的核心框架其实短小精悍剩下的全是扩展点。而扩展点能保持优雅的核心原因就是工厂模式把“对象创建”这件事从业务逻辑里彻底剥离了。5. 读源码最容易掉进去的几个误区5.1 误区一动态代理是在代理 OkHttp这个误解我见过太多次。一些文章会把 Retrofit 的动态代理解释成“代理了 OkHttp 的调用”听起来顺耳实际上不准确。动态代理的对象是开发者定义的接口代理的入口是接口方法调用而 OkHttp 是底层真正执行网络请求的它本身并没有被 Retrofit 的InvocationHandler拦截。正确的理解是动态代理把“接口方法”这一层抽象掉了让调用方感觉不到实现类的存在。OkHttp 在这里只是被OkHttpCall调用两者不是代理关系而是组合关系。5.2 误区二CallAdapter 与 Converter 分不清不少朋友面试时被问“CallAdapter 和 Converter 有什么区别”第一反应都是“两个都是用来处理返回数据的吧”。这个答案很容易被追问到底。CallAdapter处理的是调用形态决定“请求之后拿到的容器是什么”比如Call、Observable、suspendConverter处理的是数据格式决定“容器里面的内容怎么反序列化”比如 JSON 字符串变User对象。可以这样记CallAdapter管外层包装Converter管内层解析。外层是同步是异步、是响应式还是挂起函数跟数据格式没有任何关系同样数据格式用 Gson 还是 Moshi也可以独立于外层容器单独切换。5.3 误区三把“使用了模式”当成“实现了模式”有些读者看完源码会写“Retrofit 使用了装饰器模式、观察者模式”之类的结论但真拿源码去核对时往往找不到对应的接口和实现。比如观察者模式Retrofit 的异步回调是通过Callback.enqueue实现的但这只是借助了 OkHttp 的异步回调机制Retrofit 本身并没有订阅/发布模型。再比如装饰器模式某些 Converter 确实会叠加额外处理逻辑但这是为了满足接口组合并不是刻意实现装饰器。做源码分析时分清“直接实现”和“间接涉及”很重要。否则容易把知识讲串也容易在面试中一问到细节就露馅。我自己的原则是只讲源码里存在明确结构和明确意图的模式模糊地带宁可一笔带过也不要强行往经典模式上贴标签。6. 从 Retrofit 的设计模式中能搬到业务里的实战套路6.1 场景一用 Builder 管理多环境配置Android 开发里经常遇到这个问题项目有开发、测试、生产三套环境每套环境的 baseUrl、日志开关、超时时间都不一样于是不少人写了一个AppConfig类里面一堆 public 静态变量到处都能改。参照 Retrofit.Builder 的做法可以建一个EnvironmentConfig.Builder把环境名、baseUrl、日志开关设为必填项把超时时间、重试次数设为选填项并在build()阶段完成默认值填充和参数合法性校验。这样整体配置不仅可读性强还能保证运行时拿到的对象一定是完整可用的。6.2 场景二用策略模式切换加解密方案业务中经常遇到加解密方案需要切换的情况。比如接口协议从明文改成 AES再改成 RSAAES 混合如果用 if-else 到处判断每次改动都要牵动一串调用方。模仿 Converter 的思路定义一个加密策略接口不同实现类放在一起运行时通过一个工厂类根据协议版本返回对应实现。调用方永远面向策略接口不关心具体算法。这样既符合开闭原则也方便做单元测试测试时注入一个“不加密”的假实现即可。6.3 场景三用适配器模式统一回调形态业务层经常踩到另一个坑异步任务有时候用回调有时候返回Future有时候用协程代码风格很不统一。定义一个简单的适配接口public interface TaskAdapterR, T { T adapt(TaskR task); }每个业务场景自己实现适配逻辑比如把回调风格的Task适配成协程风格的suspend结果。这样做的好处是底层任务执行逻辑不需要改上层调用方式却可以按场景灵活变化。那种“底层随手换上层无感知”的效果我在实践里觉得特别受用也正是从 Retrofit 的CallAdapter上偷师来的。最后再说点个人体会。Retrofit 里的设计模式真正打动我的不是哪个模式本身多精彩而是每个模式都被用在了“最合适”的位置上动态代理换来了接口声明式编程Builder 换来了配置的可读性Adapter 和 Converter 换来了生态扩展能力。模式从来不是背出来的而是从需求里长出来的。以后你读源码不妨先问一个问题作者在这里遇到的最大麻烦是什么想清楚这个很多设计模式不用刻意认你自己就能推导出来。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询