OpenFeign 声明式调用
OpenFeign 简介
OpenFeign 是 Spring Cloud 提供的声明式 HTTP 客户端,基于 Feign 10.x 版本开发。
核心特点
- 声明式调用:通过接口和注解定义 HTTP 请求
- 集成 Ribbon:支持客户端负载均衡
- 支持熔断:可集成 Sentinel、Hystrix
- 编码解码:自动处理请求和响应的编解码
- 契约驱动:接口定义即服务契约
工作原理
┌─────────────────┐
│ 业务代码调用 │
│ @FeignClient │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Feign Client │
│ 动态代理 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Request │
│ 构建器 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ LoadBalancer │
│ 选择实例 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ HTTP Client │
│ 发送请求 │
└─────────────────┘
快速开始
1. 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2. 启用 Feign
@SpringBootApplication
@EnableFeignClients
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
3. 定义 Feign Client
@FeignClient(name = "order-service")
public interface OrderClient {
@GetMapping("/orders/{id}")
Result<Order> getOrder(@PathVariable("id") Long id);
@PostMapping("/orders")
Result<Order> createOrder(@RequestBody Order order);
@DeleteMapping("/orders/{id}")
Result<Void> deleteOrder(@PathVariable("id") Long id);
}
4. 使用 Feign Client
@Service
public class UserService {
@Autowired
private OrderClient orderClient;
public Order getUserOrder(Long userId) {
Result<Order> result = orderClient.getOrder(userId);
if (result.isSuccess()) {
return result.getData();
}
throw new BusinessException("获取订单失败");
}
}
注解详解
1. @FeignClient
@FeignClient(
name = "order-service", // 服务名
url = "http://localhost:8080", // 直接指定 URL(可选)
contextId = "orderClient", // 上下文 ID
path = "/api", // 基础路径
configuration = FeignConfig.class, // 自定义配置
fallback = OrderFallback.class, // 降级类
fallbackFactory = OrderFallbackFactory.class // 降级工厂
)
2. 请求映射注解
@FeignClient(name = "user-service")
public interface UserClient {
// GET 请求
@GetMapping("/users/{id}")
Result<User> getUser(@PathVariable("id") Long id);
// POST 请求
@PostMapping("/users")
Result<User> createUser(@RequestBody User user);
// PUT 请求
@PutMapping("/users/{id}")
Result<User> updateUser(
@PathVariable("id") Long id,
@RequestBody User user
);
// DELETE 请求
@DeleteMapping("/users/{id}")
Result<Void> deleteUser(@PathVariable("id") Long id);
// PATCH 请求
@PatchMapping("/users/{id}/status")
Result<User> updateStatus(
@PathVariable("id") Long id,
@RequestParam("status") String status
);
}
3. 请求参数注解
@FeignClient(name = "user-service")
public interface UserClient {
// 路径变量
@GetMapping("/users/{id}")
Result<User> getUser(@PathVariable("id") Long id);
// 查询参数
@GetMapping("/users/search")
Result<List<User>> searchUsers(
@RequestParam("keyword") String keyword,
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size
);
// 请求头
@GetMapping("/users/me")
Result<User> getCurrentUser(@RequestHeader("Authorization") String token);
// 表单数据
@PostMapping("/users/login")
Result<User> login(
@RequestParam("username") String username,
@RequestParam("password") String password
);
// 请求体
@PostMapping("/users")
Result<User> createUser(@RequestBody User user);
}
高级特性
1. 超时配置
feign:
client:
config:
default: # 全局配置
connectTimeout: 5000
readTimeout: 5000
loggerLevel: BASIC
order-service: # 指定服务配置
connectTimeout: 10000
readTimeout: 10000
2. 日志配置
feign:
client:
config:
default:
loggerLevel: HEADERS # NONE, BASIC, HEADERS, FULL
logging:
level:
com.example.client: DEBUG
3. 自定义配置
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
@Bean
public RequestInterceptor requestInterceptor() {
return requestTemplate -> {
requestTemplate.header("X-Request-ID", UUID.randomUUID().toString());
requestTemplate.header("X-Timestamp", String.valueOf(System.currentTimeMillis()));
};
}
@Bean
public Retryer retryer() {
return new Retryer.Default(100, 1000, 3);
}
}
4. 拦截器
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Autowired
private HttpServletRequest request;
@Override
public void apply(RequestTemplate template) {
// 传递认证信息
String token = request.getHeader("Authorization");
if (token != null) {
template.header("Authorization", token);
}
// 传递用户信息
String userId = request.getHeader("X-User-ID");
if (userId != null) {
template.header("X-User-ID", userId);
}
// 传递链路追踪 ID
String traceId = MDC.get("traceId");
if (traceId != null) {
template.header("X-Trace-ID", traceId);
}
}
}
5. 错误解码器
public class CustomErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
String body = Util.toString(response.body().asReader());
switch (response.status()) {
case 400:
return new BadRequestException(body);
case 401:
return new UnauthorizedException(body);
case 403:
return new ForbiddenException(body);
case 404:
return new NotFoundException(body);
case 500:
return new InternalServerException(body);
default:
return new FeignException(methodKey, response, body);
}
}
}
熔断降级
集成 Sentinel
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
feign:
sentinel:
enabled: true
@FeignClient(
name = "order-service",
fallback = OrderFallback.class
)
public interface OrderClient {
@GetMapping("/orders/{id}")
Result<Order> getOrder(@PathVariable("id") Long id);
}
@Component
public class OrderFallback implements OrderClient {
@Override
public Result<Order> getOrder(Long id) {
return Result.fail("服务暂时不可用");
}
}
降级工厂
@Component
public class OrderFallbackFactory implements FallbackFactory<OrderClient> {
@Override
public OrderClient create(Throwable cause) {
return new OrderClient() {
@Override
public Result<Order> getOrder(Long id) {
log.error("调用 order-service 失败", cause);
return Result.fail("服务暂时不可用");
}
};
}
}
文件上传下载
文件上传
@FeignClient(
name = "file-service",
configuration = MultipartConfig.class
)
public interface FileClient {
@PostMapping(value = "/files/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
Result<FileVO> uploadFile(
@RequestPart("file") MultipartFile file,
@RequestParam("folder") String folder
);
}
@Configuration
public class MultipartConfig {
@Bean
public Decoder feignDecoder() {
return new ResponseEntityDecoder(new SpringDecoder());
}
@Bean
public Encoder feignEncoder() {
return new SpringFormEncoder();
}
}
文件下载
@FeignClient(name = "file-service")
public interface FileClient {
@GetMapping("/files/{id}")
ResponseEntity<Resource> downloadFile(@PathVariable("id") Long id);
}
性能优化
1. 连接池配置
feign:
httpclient:
enabled: true
max-connections: 200
max-connections-per-route: 50
okhttp:
enabled: true
max-idle-connections: 50
keep-alive-duration: 300
2. 压缩配置
feign:
compression:
request:
enabled: true
mime-types: text/xml,application/xml,application/json
min-request-size: 2048
response:
enabled: true
3. 缓存配置
@Configuration
public class FeignCacheConfig {
@Bean
public RequestInterceptor cacheInterceptor() {
return template -> {
template.header("Cache-Control", "no-cache");
};
}
}
测试
单元测试
@SpringBootTest
public class OrderClientTest {
@Autowired
private OrderClient orderClient;
@Test
public void testGetOrder() {
Result<Order> result = orderClient.getOrder(1L);
assertNotNull(result);
assertTrue(result.isSuccess());
}
}
Mock 测试
@SpringBootTest
@Import({MockOrderClient.class})
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void testGetUserOrder() {
Order order = userService.getUserOrder(1L);
assertNotNull(order);
}
}
@Configuration
@ConditionalOnProperty(name = "test.mock", havingValue = "true")
public class MockOrderClientConfig {
@Bean
@Primary
public OrderClient mockOrderClient() {
return id -> Result.success(new Order());
}
}
常见问题
1. 404 错误
问题:Feign 调用返回 404
解决方案:
- 检查服务名是否正确
- 检查路径配置
- 检查请求参数编码
2. 超时错误
问题:Feign 调用超时
解决方案:
- 调整超时配置
- 优化服务端性能
- 增加重试机制
3. 中文乱码
问题:中文参数乱码
解决方案:
@Bean
public RequestInterceptor charsetInterceptor() {
return template -> {
template.header("Content-Type", "application/json;charset=UTF-8");
};
}
总结
OpenFeign 通过声明式接口简化了服务间调用,支持负载均衡、熔断降级、日志记录等特性。
合理使用 Feign 的配置和扩展机制,可以提高服务调用的可靠性和可维护性。
在生产环境中,建议配置合理的超时时间,实现熔断降级,并添加完善的日志记录。