bigywater.tech
Spring · 119

OpenFeign:声明式客户端怎么生成代理

@FeignClient 的接口是怎么变成能发 HTTP 请求的对象的?注解会变成什么样的请求?为什么只写 name 会启动失败?错误是启动时还是调用时暴露?这篇在 Spring Boot 4.0.8、Spring Cloud OpenFeign 5.0.3 上用一个会记录请求的后端逐项看。

JDK 动态代理
@FeignClient 接口的实现类是 demo.$Proxy,InvocationHandler 是 FeignInvocationHandler
启动失败
只引入 spring-cloud-starter-openfeign、不写 url 时,缺少 loadbalancer 的错误信息
启动时报错
一个方法两个 @RequestBody:Method has too many Body parameters

运行时是什么

实测 · 本机执行输出
  应用刚启动、还没有发过任何 Feign 调用时,FeignClientFactory 里的上下文 = [orders](这个请求本身 31 ms,先把 DispatcherServlet 预热掉)
  第一次调用 GET /get → {"ok": true},耗时 14 ms
  后面 5 次调用耗时的中位数 2 ms
  接口 OrdersApi 的实现类 = demo.$ProxyN,Proxy.isProxyClass = true,InvocationHandler = feign.ReflectiveFeign$FeignInvocationHandler
    工厂 bean = FeignClientFactoryBean;FeignClientFactory 里的上下文 = [orders]
    Encoder = …openfeign.support.SpringEncoder
    Decoder = feign.optionals.OptionalDecoder
    Contract = …openfeign.support.SpringMvcContract
    Client = 无
    Retryer = feign.Retryer$1
    Logger = 无
    ErrorDecoder = 无
    Options = 无
  default 方法:default 方法在本地执行

JDK 动态代理;注入时创建;每个客户端名一个子上下文,里面有 Encoder、Decoder、Contract、Retryer。

注解到请求

实测 · 本机执行输出
  api.get("42", true, "t-1") → GET /orders/42?verbose=true,X-Trace = t-1,Accept = */*,User-Agent = Java/21.0.12.1,HTTP 版本 HTTP/1.1
  api.create(new Item("A1", 3)) → POST /orders,Content-Type = application/json,请求体 = {"id":"A1","qty":3};调用方拿到 Item[id=A1, qty=3]
  api.search(@SpringQueryMap new Query("bob", 2)) → GET /search?name=bob&page=2

路径、查询参数、请求头、JSON 请求体、@SpringQueryMap 展开。

只写 name

实测 · 本机执行输出
  只引入 spring-cloud-starter-openfeign(没有 loadbalancer):应用启动失败:Caused by: java.lang.IllegalStateException: No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?
  加上 spring-cloud-starter-loadbalancer:lb.get("7", false, "t-2") → 后端收到 GET /orders/7?verbose=false,X-Trace = t-2;调用方拿到 {"ok": true}
  Client = …openfeign.loadbalancer.FeignBlockingLoadBalancerClient;FeignClientFactory 里的上下文 = [lb-orders]

需要 loadbalancer starter,否则启动失败;有了之后 Client 是 FeignBlockingLoadBalancerClient。

两个 RequestBody

实测 · 本机执行输出
  应用启动失败:Caused by: java.lang.IllegalStateException: Method has too many Body parameters: public abstract demo.Main$Item demo.Main$OrdersApi.create(demo.Main$Item,demo.Main$Item)

创建代理时就报错,不会拖到第一次调用。

分步演示

OpenFeign
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。后端是 Python 线程里的 HTTP 服务,记录收到的方法、路径、请求头与请求体;「只写 name」的实验实例来自 SimpleDiscoveryClient 的配置
    • 没有验证:默认 Client 是不是 HttpURLConnection(只从 User-Agent 推断);换成 Apache HttpClient 5 / OkHttp / JDK HttpClient 的方式与差异;Logger 级别与 FeignClientConfiguration 的覆盖规则;@FeignClient 的 contextId 与 path;Feign 的 Micrometer 观测;FeignBuilderCustomizer;spring.cloud.openfeign.lazy-attributes-resolution;在 GraalVM 原生镜像里的行为
    京ICP备2026067502号-1京公网安备11010502063487号