Feign 深度解析
Feign 深度解析
Feign 作为 Spring Cloud 生态中的声明式 HTTP 客户端,通过优雅的接口注解方式简化了服务间调用。本文将深入解析 Feign 的核心用法,并通过代码示例演示各种实战场景。
一、Feign 基础使用
1.1 环境搭建
添加 Maven 依赖(Spring Cloud 2021.0.1 版本):
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.1.0</version>
</dependency>
1.2 接口声明示例
@FeignClient(name = "user-service", url = "http://api.example.com")
public interface UserServiceClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable("id") Long userId);
@PostMapping("/users")
User createUser(@RequestBody User user);
@GetMapping("/users/search")
List<User> searchUsers(@RequestParam("name") String name,
@RequestParam("age") int age);
}
1.3 启用 Feign
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
二、高级配置详解
2.1 超时与重试配置
application.yml 配置示例:
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 10000
loggerLevel: full
user-service:
connectTimeout: 3000
readTimeout: 5000
retryer:
maxAttempts: 3
backoff: 1000
2.2 自定义配置类
@Configuration
public class FeignConfig {
@Bean
public Retryer retryer() {
return new Retryer.Default(1000, 3000, 3);
}
@Bean
public ErrorDecoder errorDecoder() {
return new CustomErrorDecoder();
}
}
三、异常处理机制
3.1 基础异常捕获
try {
return userServiceClient.getUserById(userId);
} catch (FeignException e) {
if (e.status() == 404) {
throw new NotFoundException("User not found");
}
log.error("Feign call failed: {}", e.contentUTF8());
throw new ServiceException("Remote service error");
}
3.2 自定义错误解码器
public class CustomErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400) {
String body = Util.toString(response.body().asReader());
return new CustomException(
"API Error: " + response.status(),
body
);
}
return new Default().decode(methodKey, response);
}
}
四、拦截器实战
4.1 认证拦截器
public class AuthInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
String token = SecurityContext.getCurrentToken();
template.header("Authorization", "Bearer " + token);
// 添加自定义追踪头
template.header("X-Trace-Id", UUID.randomUUID().toString());
}
}
4.2 日志拦截器
public class LoggingInterceptor implements RequestInterceptor {
private static final Logger log = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public void apply(RequestTemplate template) {
log.debug("Request to {}: {}", template.url(), template.body());
}
}
注册拦截器:
@Configuration
public class FeignConfig {
@Bean
public AuthInterceptor authInterceptor() {
return new AuthInterceptor();
}
}
五、动态 URL 调用
5.1 直接指定 URL
@FeignClient(name = "dynamic-service", url = "${external.api.url}")
public interface ExternalServiceClient {
@PostMapping("/process")
Response processData(@RequestBody Payload payload);
}
/**
* 请求拦截器动态改写目标地址
*/
public class DynamicInterceptor implements RequestInterceptor {
private final RoutingService routingService;
@Override
public void apply(RequestTemplate template) {
String target = routingService.resolve(template.feignTarget().name());
template.target(target); // 根据策略引擎动态设置地址:ml-citation{ref="2,5" data="citationList"}
}
}
5.2 RequestLine 方式
@FeignClient(name = "custom-client")
public interface CustomClient {
@RequestLine("GET /v2/{resource}")
String getResource(@Param("resource") String res, @Param("locale") String locale);
}
六、性能优化建议
-
连接池配置:
feign: httpclient: enabled: true max-connections: 200 max-connections-per-route: 50
-
GZIP 压缩:
feign: compression: request: enabled: true mime-types: text/xml,application/xml,application/json response: enabled: true
-
缓存策略:
@Cacheable("users") @GetMapping("/users/{id}") User getUserById(@PathVariable("id") Long userId);
七、常见问题排查
7.1 405 Method Not Allowed
可能原因:
- 错误使用 GET 请求传递 Body
- 路径参数未正确匹配
解决方案:
// 错误示例
@GetMapping("/update")
void updateUser(@RequestBody User user); // GET 方法不能有请求体
// 正确修改
@PostMapping("/update")
void updateUser(@RequestBody User user);
7.2 序列化异常
处理方案:
@Bean
public Encoder feignEncoder() {
return new JacksonEncoder(customObjectMapper());
}
private ObjectMapper customObjectMapper() {
return new ObjectMapper()
.registerModule(new JavaTimeModule())
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
八、最佳实践
- 接口隔离原则:每个 Feign 客户端对应一个微服务
- 版本控制:在路径中包含 API 版本号
@FeignClient(name = "order-service", url = "${order.service.url}/v1")
- 熔断集成:
@FeignClient(name = "payment-service", fallback = PaymentFallback.class) public interface PaymentClient { // ... }
附录:常用配置参考表
配置项 | 默认值 | 说明 |
---|---|---|
connectTimeout | 10s | 建立连接超时时间 |
readTimeout | 60s | 读取响应超时时间 |
loggerLevel | NONE | 日志级别(BASIC, HEADERS, FULL) |
retry.maxAttempts | 5 | 最大重试次数 |
retry.backoff | 100ms | 重试间隔基数 |