跳转到内容

客户端配置隔离

OpenFeign 没有把所有客户端 Bean 放进同一个应用上下文,而是用 FeignClientFactory 为每个客户端维护一个命名子上下文。这是“同一应用中多个下游拥有不同 Codec、超时和拦截器”的基础。

先给答案:每个 Feign 客户端需要子上下文来隔离“同类型、不同服务”的配置

Section titled “先给答案:每个 Feign 客户端需要子上下文来隔离“同类型、不同服务”的配置”

不同客户端可能使用不同 decoder、interceptor、超时或负载策略,但这些 Bean 类型名称又高度相似。为每个客户端建立独立配置上下文,可以让默认配置共享,同时让特定客户端覆盖自己的组件,而不污染全局 Spring 容器。

隔离并非完全独立:父上下文仍提供基础 Bean,属性与 Bean 覆盖还存在优先级。配置排查要明确 Bean 是从父上下文继承、子上下文创建,还是被属性覆盖。

ApplicationContext
├─ FeignClientFactory
│ ├─ orders-context
│ │ └─ FeignClientsConfiguration + orders config
│ └─ users-context
│ └─ FeignClientsConfiguration + users config
└─ shared infrastructure

FeignClientFactory:40 继承 NamedContextFactory<FeignClientSpecification>。FeignAutoConfiguration#feignContext:102-107 创建工厂并设置 FeignClientSpecification 列表;工厂提供 getInstance 和 getInstanceWithoutAncestors,见 FeignClientFactory:52-65。

替代方案:所有 Feign 客户端共享一组全局 Encoder、Request.Options 和 RequestInterceptor。

为什么不行:不同下游常常拥有不同认证、超时、序列化格式和重试边界;全局 Bean 会导致配置相互污染,也无法让同一服务的两套访问策略共存。

设计取舍:子上下文增加了 Bean 查找和生命周期复杂度,但换来明确的配置作用域。FeignClientFactoryBean 通过 getInheritedAwareOptional、getInstances 等方法控制是否继承父上下文,见 FeignClientFactoryBean:402-428。

客户端配置通常由两层组成:FeignClientsConfiguration 提供默认 Bean,@FeignClient(configuration=...) 或属性类提供覆盖。FeignClientFactoryBean#feign:140-258 先读取子上下文的 Encoder、Decoder、Contract、Logger、Options 和 Capability,再由属性配置覆盖具体 Builder 设置。

当前版本还包含 FeignChildContextInitializer 和 FeignClientBeanFactoryInitializationAotProcessor,用于在 AOT/native 场景提前生成客户端子上下文初始化代码。运行时依赖反射的接口代理、fallback 和泛型类型必须被运行时提示覆盖,不能只按 JVM 反射路径验证。

场景 现象 原因 规避
配置类被主应用扫描 默认 Bean 被意外覆盖 客户端专属配置进入了父上下文 把配置类放到扫描范围外或使用明确 package
多客户端共用 contextId 超时/拦截器串用 子上下文键相同 每套配置使用唯一 contextId
父上下文 Bean 不可见 自定义组件找不到 使用了 without ancestors 查找 判断组件是否应共享,再选择注册层级
native 镜像缺少提示 运行时代理或 fallback 失败 动态类型未登记 使用项目提供的 AOT 处理链并覆盖自定义类型

命名子上下文适用于插件隔离、租户隔离和多数据源系统:父上下文承载共享基础设施,子上下文承载局部策略。隔离键必须稳定且可观测,否则排查“哪个配置生效”会非常困难。

面试锚点

  • FeignClientFactory 为什么继承 NamedContextFactory?
  • 父上下文 Bean 和客户端子上下文 Bean 如何取舍?
  • name 与 contextId 哪个决定配置隔离?