OpenFeign
开篇:微服务之间怎么互相调用?
在单体应用里,一个模块调另一个模块,就是普通的方法调用——写个 userService.getUser(id) 就完事了。但微服务拆分后,用户服务和订单服务跑在不同的机器上,方法调用变成了网络请求。
最原始的做法是用 RestTemplate 手动构造 HTTP 请求:
// 手动拼 URL,手动解析响应,一堆模板代码
String url = "http://user-service/api/users/" + userId;
UserDTO user = restTemplate.getForObject(url, UserDTO.class);这写起来累,维护起来更累——到处散落着 URL 拼接和 HTTP 调用的代码。
OpenFeign 的出现就是为了解决这个痛点:让远程调用像本地方法调用一样简单。你只需要定义一个接口,加上注解,剩下的 HTTP 请求构造、序列化、负载均衡全部由框架搞定。
// 声明式接口,看起来就像调本地方法
@FeignClient("user-service")
public interface UserClient {
@GetMapping("/api/users/{id}")
UserDTO getUser(@PathVariable("id") Long id);
}
// 调用方直接注入使用
@Autowired
private UserClient userClient;
UserDTO user = userClient.getUser(123L); // 就这么简单一、OpenFeign 使用
Feign vs OpenFeign
Feign 是 Netflix 开源的声明式 HTTP 客户端。OpenFeign 是 Spring Cloud 对 Feign 的增强,支持 Spring MVC 注解(如 @GetMapping、@RequestParam),并集成了 Ribbon(负载均衡)和 Hystrix(熔断)。
简单说,OpenFeign = Feign + Spring MVC 注解支持 + Spring Cloud 生态集成。
注意:从 Spring Cloud 2020 开始,Feign 不再维护。Spring Cloud 2022 宣布 OpenFeign 进入"功能完结"状态——只修 bug,不加新功能。Spring 6.0 推出的
@HttpExchange是官方推荐的替代方案。
快速上手
1. 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>2. 启动类开启 Feign
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}3. 定义 Feign 客户端接口
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/api/users/{id}")
UserDTO getUser(@PathVariable("id") Long id);
@PostMapping("/api/users")
UserDTO createUser(@RequestBody UserDTO user);
@GetMapping("/api/users")
List<UserDTO> listUsers(@RequestParam("name") String name);
}4. 注入并调用
@Service
public class OrderService {
@Autowired
private UserClient userClient;
public OrderDTO createOrder(Long userId) {
// 远程调用,但看起来就像本地调用
UserDTO user = userClient.getUser(userId);
// ... 创建订单逻辑
}
}二、底层原理
OpenFeign 的核心原理是动态代理。当你注入 UserClient 时,Spring 容器里其实是一个 JDK 动态代理对象。调用接口方法时,代理会:
- 根据注解解析出目标 URL(服务名 + 路径 + 参数)
- 通过负载均衡器(Ribbon / LoadBalancer)从注册中心获取服务实例列表,选择一个
- 构造 HTTP 请求并发送
- 将响应反序列化为接口定义的返回类型
Feign 的组件体系
OpenFeign 是高度可定制的,核心组件包括:
| 组件 | 作用 | 默认值 |
|---|---|---|
| Client | 底层 HTTP 客户端 | JDK URLConnection(无连接池) |
| Contract | 注解解析规则 | SpringMVC Contract |
| Encoder | 请求编码器 | SpringEncoder |
| Decoder | 响应解码器 | ResponseEntityDecoder |
| Logger | 日志 | Slf4jLogger |
| RequestInterceptor | 请求拦截器 | 无 |
默认的
Feign.Client.Default用的是 JDK 的 HttpURLConnection,没有连接池,性能不好。生产环境建议替换成 Apache HttpClient 或 OkHttp。
三、超时与重试配置
在分布式环境中,网络波动是常态。合理配置超时和重试非常重要。
超时配置
feign:
client:
config:
default: # 全局默认配置
connectTimeout: 5000 # 连接超时 5 秒
readTimeout: 10000 # 读取超时 10 秒
user-service: # 针对特定服务的配置
connectTimeout: 3000
readTimeout: 5000日志级别
Feign 默认不打印任何日志。开发调试时可以打开:
logging:
level:
com.example.client: debug
feign:
client:
config:
default:
loggerLevel: FULL| 级别 | 内容 |
|---|---|
| NONE(默认) | 不打印 |
| BASIC | 请求方法、URL、响应状态码、耗时 |
| HEADERS | BASIC + 请求/响应 Header |
| FULL | 全部(Header + Body + 元数据) |
生产环境建议用 BASIC 或 NONE,FULL 会打印大量日志影响性能。
四、Feign 拦截器
在微服务调用中,经常需要传递一些公共信息,比如认证 Token、链路追踪的 traceId、用户身份等。如果每个接口方法都手动添加 Header,太繁琐了。
这时候可以用 RequestInterceptor,统一为所有 Feign 请求添加 Header。
示例:传递认证 Token
public class AuthRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 从当前请求上下文中获取 Token
ServletRequestAttributes attrs = (ServletRequestAttributes)
RequestContextHolder.getRequestAttributes();
if (attrs != null) {
String token = attrs.getRequest()
.getHeader("Authorization");
template.header("Authorization", token);
}
}
}注册为 Bean:
@Configuration
public class FeignConfig {
@Bean
public AuthRequestInterceptor authInterceptor() {
return new AuthRequestInterceptor();
}
}这样所有 Feign 请求都会自动带上 Authorization 头,不需要每个接口手动传。
替换 HTTP 客户端
默认的 URLConnection 没有连接池,建议替换为 Apache HttpClient:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>feign:
httpclient:
enabled: true
max-connections: 200
max-connections-per-route: 50五、常见面试题精选
Q1:OpenFeign 的底层原理是什么?
核心是 JDK 动态代理。注入的 Feign 接口实际上是一个代理对象。调用方法时,代理根据注解解析出 HTTP 请求信息(URL、参数、Header),通过负载均衡器选择一个服务实例,发送 HTTP 请求并将响应反序列化为返回类型。
Q2:OpenFeign 和 Dubbo 有什么区别?
OpenFeign 基于 HTTP 协议,声明式调用,与 Spring Cloud 生态深度集成。Dubbo 基于自定义 RPC 协议,性能更高,适合高频内部调用。简单说,OpenFeign 是 HTTP 版的远程调用,Dubbo 是 RPC 版的远程调用。
Q3:OpenFeign 不维护了怎么办?
Spring 6.0 推出了 @HttpExchange 作为替代。它是 Spring 内置的声明式 HTTP 客户端,不需要额外依赖,用法和 Feign 类似。新项目可以考虑直接用 @HttpExchange,老项目 OpenFeign 继续用也没问题(只是不加新特性了,bug 还会修)。
小结
OpenFeign 的核心价值是让远程调用像本地调用一样简单。掌握以下要点就够了:
- 声明式调用:定义接口 + 注解,不用手写 HTTP 请求
- 动态代理:底层是 JDK 代理 + 注解解析 + 负载均衡 + HTTP 客户端
- 拦截器:用 RequestInterceptor 统一处理认证、链路追踪等公共逻辑
- 替换 HTTP 客户端:默认的 URLConnection 性能差,生产环境换成 Apache HttpClient
- 演进方向:OpenFeign 功能完结,Spring 6.0 的 @HttpExchange 是未来