一句话总结
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 传递上下文。