Skip to content

OpenFeign独立面试题

本页只保留面试标准回答、追问方向和原理跳转。完整源码、Demo、失败窗口和生产Runbook统一放在OpenFeign内部原理与生产治理

1. OpenFeign是什么

标准回答: OpenFeign是声明式HTTP客户端。开发者定义Java接口和HTTP注解,框架在启动阶段生成代理对象;调用接口方法时,代理把参数填入请求模板,经编码、服务发现、负载均衡和HTTP Client发送,最后把响应解码为Java返回值。写法像本地调用,本质仍是可能超时、丢包、重复执行的远程调用。

原理:完整心智模型

2. @FeignClient只有接口,为什么能注入

标准回答: @EnableFeignClients导入FeignClientsRegistrar,Registrar扫描带@FeignClient的接口并注册FeignClientFactoryBean。FactoryBean组装Feign组件,通过Targeter和Feign Core创建JDK动态代理,Spring注入的是代理对象,不是普通实现类。

原理:启动入口FactoryBean

3. Feign为什么使用JDK动态代理

标准回答: Feign目标天然是接口,不需要继承实现类。ReflectiveFeign为每个接口方法创建MethodHandler映射,再通过Proxy.newProxyInstance创建实现该接口的代理。调用普通方法时InvocationHandler按Method从dispatch中取Handler执行。

原理:代理创建

4. Feign每次请求都会重新解析注解吗

标准回答: 不会。代理创建阶段由Contract解析接口方法,形成MethodMetadata和MethodHandler映射;每次请求只根据缓存的元数据和本次实参创建RequestTemplate。把启动期元数据解析和运行期参数填充区分开,才能解释首次创建失败与请求阶段失败的差异。

原理:Contract与模板

5. FeignClientsRegistrarFeignClientFactoryBean分别做什么

标准回答: Registrar负责发现接口、校验接口、注册客户端配置和BeanDefinition;FactoryBean负责取得客户端专属上下文,组装Builder、Encoder、Decoder、Contract、Client等组件,并创建最终代理。前者发生在定义注册阶段,后者生产可注入对象。

原理:源码对象地图

6. namecontextId有什么区别

标准回答: name主要表示目标服务名,未配置固定URL时会参与服务发现;contextId主要标识Feign客户端命名子容器和专属配置。同一个服务可以有多个Client,通过不同contextId配置不同超时、日志和错误处理。重复contextId可能造成配置或Bean冲突。

原理:命名子容器

7. url属性会不会继续走注册中心负载均衡

标准回答: 通常不会。没有URL时,FactoryBean按服务名创建逻辑Target并使用LoadBalancer Client;配置固定URL时,会按固定地址创建Target,并在源码中解开LoadBalancer Client包装,直接使用delegate HTTP Client。因此生产被固定URL环境变量覆盖时,Nacos实例变化不会改变请求目标。

原理:固定URL与服务名分流

8. FeignClientFactory为什么需要子容器

标准回答: 不同下游需要不同的Encoder、Decoder、ErrorDecoder、RequestInterceptor、超时和认证。FeignClientFactory按contextId维护命名上下文,让每个客户端拥有专属Bean组合,同时可按配置继承父容器。客户端配置类若被主应用扫描,可能意外污染所有Client。

原理:命名子容器

9. Contract、Encoder和Decoder分别负责什么

标准回答: Contract把接口注解解析为HTTP方法、路径和参数位置;Encoder把请求体对象序列化为JSON、表单或multipart字节;Decoder把成功HTTP响应转换成方法声明的Java类型。它们都不负责选实例和建立TCP连接。

原理:Contract与模板请求编码

10. RequestInterceptor何时执行

标准回答: MethodHandler根据实参创建本次RequestTemplate后,发送前依次执行RequestInterceptor,再由Target生成Request。它适合添加Trace、受控Token和白名单Header,不适合做慢查询、创建HTTP Client或无条件透传所有入站Header。

原理:RequestInterceptor边界

11. Feign怎样调用到真实IP和端口

标准回答: 原始请求URL主机名是服务名。FeignBlockingLoadBalancerClient从host提取serviceId,调用Spring Cloud LoadBalancer选出ServiceInstance,用实例host、port和协议重建URL,再交给底层HTTP Client发送。Nacos只提供实例数据,不转发业务请求。

原理:LoadBalancer桥接

12. Spring Cloud中到底是谁负责发请求

标准回答: 真正负责建立连接并发送HTTP报文的是底层HTTP Client,例如Apache HttpClient、OkHttp、JDK Client或Reactor Netty。Feign负责接口代理、注解解析、请求模板、编解码和错误处理;Nacos负责注册发现和实例数据;Spring Cloud LoadBalancer负责从实例列表里选择一个ServiceInstance,并把逻辑服务名替换为真实IP和端口。不能说Nacos转发业务请求,也不能说Feign直接依赖Nacos完成全部调用。

原理:服务调用链路中的职责划分Feign、Nacos、LoadBalancer和HTTP Client分工

13. Feign会不会每次请求都访问Nacos Server

标准回答: 通常不会。调用方会通过Nacos Client订阅或刷新实例数据,并维护本地服务实例快照;LoadBalancer选择实例时读取当前候选列表。这样能避免注册中心进入每一次业务请求的同步链路。实例上下线存在传播窗口,所以生产还要配合超时、重试预算、熔断、健康检查和优雅下线。

原理:服务调用链路Nacos内部原理

14. 注册中心里有实例,Feign为什么仍可能报无实例

标准回答: 控制台有实例只证明服务端注册表有数据。调用方可能使用不同namespace或group,本地缓存未更新,实例被健康、zone、版本、hint或权重规则过滤,或者服务名配置错误。应查看调用方实际候选列表和最终过滤链,不只看控制台截图。

原理:LoadBalancer桥接LoadBalancer内部原理

15. Feign真正使用什么发HTTP请求

标准回答: Feign Core定义Client扩展点,真正网络实现可能是Apache HttpClient 5、OkHttp、JDK HTTP Client或自定义Client,由classpath和自动配置条件决定。Feign负责代理和协议编排,底层Client负责DNS、TCP、TLS、连接池、发送和读取。

原理:HTTP Client

16. 连接池等待超时和连接超时有什么区别

标准回答: 连接池等待超时是线程在本地等待空闲连接,可能尚未开始连接下游;连接超时是已经尝试和目标地址建立TCP连接但未在时限内完成。前者优先查连接池容量、每路由上限和连接泄漏,后者查IP、端口、网络、实例和防火墙。

原理:连接池超时层次

17. Read Timeout是否表示下游执行失败

标准回答: 不是。下游可能已经提交事务,只是响应在返回途中丢失或超过调用方读取时限。写接口出现读取超时属于结果不确定,必须按幂等键查询最终状态或执行补偿,不能无幂等地立即重试,也不能直接认定失败。

原理:超时不确定性

18. Feign默认会重试吗

标准回答: 必须限定语境。Feign Core有捕获RetryableException并交给Retryer的重试循环;但Spring Cloud OpenFeign 4.1.1样本默认注册Retryer.NEVER_RETRY,所以集成后的Feign Core层默认不重试。LoadBalancer的Spring Retry又是另一套机制,要检查依赖和配置。

原理:三层重试

19. Feign重试和LoadBalancer重试有什么区别

标准回答: Feign Core重试发生在SynchronousMethodHandler的Retryer循环,通常由RetryableException触发;LoadBalancer重试由RetryableFeignBlockingLoadBalancerClient和Spring Retry等组件执行,可携带上次实例并重新选址。两层同时开启会乘法放大请求。

原理:三层重试

20. 为什么ErrorDecoder返回普通异常不会自动重试

标准回答: Feign Core重试循环捕获的是RetryableException。ErrorDecoder返回普通业务异常时会直接向上抛出;只有返回RetryableException且Retryer允许,才可能进入Core重试。即使技术上能重试,写接口仍必须先满足幂等和总预算。

原理:错误分类重试边界

21. Encoder、Decoder和ErrorDecoder怎样分流

标准回答: Encoder处理请求Body;成功状态通常由Decoder处理;非成功HTTP状态通常交给ErrorDecoder映射异常。若系统用HTTP 200承载业务失败码,ErrorDecoder不会自然触发,需要统一Decoder或业务网关检查响应码。

原理:响应链错误分类

22. dismiss404有什么风险

标准回答: 它可让特定404进入正常Decoder路径,适合“资源不存在就是空结果”的明确查询语义;若在库存、权限或路由错误场景滥用,会把真实接口错误误解为正常空值。必须按接口语义决定,不能作为消除404异常的通用开关。

原理:响应链

23. Fallback和FallbackFactory怎样选择

标准回答: Fallback提供固定降级实现,通常不直接暴露原始cause;FallbackFactory创建降级对象时能获得失败原因,更适合区分无实例、超时、下游5xx、解码失败和熔断。二者都不能把支付或扣库存失败伪装成成功。

原理:CircuitBreaker与Fallback

24. 配置了fallback为什么不执行

标准回答: fallback属性本身不等于熔断能力已经启用。要确认Spring Cloud CircuitBreaker实现、CircuitBreakerFactory、相关开关和Targeter是否生效,并检查失败是否发生在代理创建之前。通过条件报告和实际代理链判断,不要只检查fallback Bean是否存在。

原理:CircuitBreaker接入点

25. Feign FULL日志为什么不适合长期打开

标准回答: FULL会记录请求和响应细节,部分实现为了记录Body需要读取并重新缓冲,增加内存、日志IO和延迟;同时可能泄露Token、身份证、支付或医疗数据。生产只应短时、定向、脱敏开启,并限制报文大小。

原理:响应处理安全边界

26. 为什么Feign耗时可能大于配置的Read Timeout

标准回答: Read Timeout通常只约束读取响应阶段。此前可能有连接池等待、DNS、TCP、TLS、发送时间;外层还可能有Feign Retry、LoadBalancer Retry或业务重试。因此必须按阶段观测并设置总Deadline,不能把单阶段Read Timeout理解成完整调用上限。

原理:超时层次重试边界

27. Feign调用线程会不会一直占着Tomcat线程

标准回答: 普通同步Feign调用会阻塞当前调用线程等待连接和响应,通常就是上游Servlet工作线程。大量慢下游会占满上游线程并形成级联故障。异步、WebClient或Java 21虚拟线程可以改变线程成本,但不能增加数据库连接和下游容量。

原理:HTTP Client与资源

28. 为什么Feign不建议直接传Entity

标准回答: Entity是持久化模型,可能包含懒加载代理、双向关系和内部字段,随ORM和表结构变化;远程协议需要稳定、可版本化的DTO。直接传Entity会耦合服务数据库模型,导致序列化递归、字段泄露和跨服务升级困难。

原理:请求编码

29. 怎样设计扣库存Feign调用

标准回答: 调用前生成稳定幂等键;下游用数据库唯一约束和事务原子保存幂等记录与扣减结果;调用方设置短而合理的超时,不做无幂等重试;超时后按幂等键查状态,仍不确定则进入补偿和告警。Fallback不能默认返回扣减成功。

原理:商业订单场景

30. 怎样排查Feign调用慢

标准回答: 先用Trace和时间线区分代理/编码、实例选择、连接池等待、建连、TLS、等待首字节、读取和解码;再看是否存在多层重试。线程栈停在连接池acquire说明请求可能尚未发出,停在socket read才重点查下游执行和网络。

原理:生产Runbook

31. Boot 2与Boot 3的Feign服务能否互相调用

标准回答: 可以。远程边界是HTTP和数据契约,不要求两端使用相同Java或Boot版本。迁移期间要保持URL、状态码、JSON字段、日期格式、认证和错误契约兼容,并用契约测试验证;代码内部的javaxjakarta差异不会直接穿过JSON边界。

原理:JDK 8存量边界

32. 线上Feign失败最先保留哪些证据

标准回答: 保存时间、Trace ID、调用方实例、Feign客户端和方法、完整异常第一处cause、最终目标实例、状态码、尝试次数、各阶段耗时、配置版本和下游是否收到幂等键。没有这些证据,重启或调大超时只能暂时改变现象,不能定位根因。

原理:生产Runbook

关联学习