远程调用 Feign/OpenFeign?

2025年 阅读约 15 分钟 面试指南 · Spring Cloud

深入解析Feign/OpenFeign远程调用:声明式HTTP客户端原理、负载均衡集成、超时重试配置、请求拦截器、日志级别、Feign vs RestTemplate,附面试模拟问答。

一句话总结

Feign 是声明式 HTTP 客户端,只需定义接口 + 注解,即可实现远程调用,无需手动写 HTTP 请求代码。核心原理:动态代理(JDK Proxy)→ 解析注解(@FeignClient、@GetMapping)→ 构建 HTTP 请求 → 发送请求 → 解析响应。OpenFeign 在 Feign 基础上集成了 Spring MVC 注解支持负载均衡(Ribbon/LoadBalancer)。Feign 默认使用 JDK HttpURLConnection,可替换为 HttpClient 或 OkHttp 提升性能。

初级理解

Feign 基本使用

# 1. 引入依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> # 2. 启动类添加 @EnableFeignClients @SpringBootApplication @EnableFeignClients public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } } # 3. 定义 Feign 接口 @FeignClient(name = "user-service") // 服务名 public interface UserFeignClient { @GetMapping("/user/{id}") User getUser(@PathVariable("id") Long id); @PostMapping("/user") Result createUser(@RequestBody UserDTO dto); } # 4. 使用(像调用本地方法一样) @Service public class OrderService { @Autowired private UserFeignClient userClient; public Order createOrder(Long userId) { User user = userClient.getUser(userId); // 远程调用 // ... } }

中级深入

Feign 原理 — 动态代理

# Feign 执行流程 # 1. @EnableFeignClients 扫描 @FeignClient 接口 # 2. 为每个接口创建 JDK 动态代理 # 3. 调用接口方法时 → 代理拦截 # 4. 解析方法上的注解(@GetMapping、@PathVariable) # 5. 构建 RequestTemplate(URL、参数、Header) # 6. 通过 LoadBalancer 选择服务实例 # 7. 发送 HTTP 请求 # 8. 解析响应,返回结果 # 核心类 # FeignClientFactoryBean:创建 Feign Client 的工厂 Bean # ReflectiveFeign:通过反射生成动态代理 # SynchronousMethodHandler:处理方法调用 # LoadBalancerFeignClient:带负载均衡的 HTTP 客户端

超时与重试配置

# Feign 超时配置 feign: client: config: default: connectTimeout: 5000 # 连接超时(毫秒) readTimeout: 10000 # 读取超时(毫秒) user-service: # 针对特定服务 connectTimeout: 3000 readTimeout: 5000 # 重试配置 spring: cloud: loadbalancer: retry: enabled: true # 自定义重试策略 @Bean public Retryer feignRetryer() { return new Retryer.Default(100, 1000, 3); // period=100ms, maxPeriod=1000ms, maxAttempts=3 }

高级拓展

Feign 拦截器

# 请求拦截器:统一添加 Header(如 Token) @Component public class FeignAuthInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { // 从当前请求上下文获取 Token String token = RequestContextHolder.getCurrentToken(); template.header("Authorization", "Bearer " + token); } } # 使用场景 # 1. 传递认证 Token # 2. 传递链路追踪 ID(TraceId) # 3. 添加公共请求头 # 注意:Feign 调用是新线程,ThreadLocal 会丢失 # 需要使用 RequestContextHolder 或 HystrixConcurrencyStrategy 传递上下文

Feign 性能优化

# 1. 替换底层 HTTP 客户端 # 默认使用 HttpURLConnection(无连接池) # 替换为 HttpClient(连接池) feign: httpclient: enabled: true max-connections: 200 max-connections-per-route: 50 # 或替换为 OkHttp feign: okhttp: enabled: true # 2. 使用压缩 feign: compression: request: enabled: true mime-types: text/xml,application/json min-request-size: 2048 response: enabled: true # 3. 日志级别 logging: level: com.example.feign.UserFeignClient: DEBUG # 只打印该接口日志

实战场景

场景:Feign 调用传递文件

# Feign 文件上传 @FeignClient(name = "file-service", configuration = FeignConfig.class) public interface FileFeignClient { @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) Result upload(@RequestPart("file") MultipartFile file); } # 配置类 @Configuration public class FeignConfig { @Bean public Encoder feignEncoder() { return new SpringFormEncoder(); } } # 调用方 @Autowired private FileFeignClient fileClient; public void upload(MultipartFile file) { Result result = fileClient.upload(file); }

面试模拟

面试官:Feign 的原理是什么?

你:基于 JDK 动态代理。@FeignClient 接口被扫描后,为每个接口创建代理对象。调用接口方法时,代理拦截并解析注解构建 HTTP 请求,通过 LoadBalancer 选择服务实例,发送请求并解析响应。本质是将接口方法调用转换为 HTTP 请求。

面试官:Feign 如何传递 Token?

你:通过 RequestInterceptor 拦截器,在 apply 方法中向 RequestTemplate 添加 Header。需要注意 Feign 调用是新线程,ThreadLocal 会丢失,需要通过 RequestContextHolder 传递上下文。