Feign异常需分层拦截与统一兜底:网络层失败、HTTP状态码异常、业务逻辑错误须分别处理,并配置超时与重试边界;自定义ErrorDecoder转化异常,配合fallbackFactory实现容错降级。

Feign 远程调用异常不能靠 try-catch 硬扛,关键在于分层拦截和统一兜底:网络层失败、HTTP 状态码异常、业务逻辑错误要分开应对,同时必须配置超时与重试边界。
Feign 默认异常触发时机
Feign 在以下情况会抛出异常:
- 网络不可达、连接超时、读取超时 → 抛出 RetryableException(默认不重试)
- HTTP 响应状态码为 4xx/5xx(如 404、500)→ 默认抛出 FeignException,但不会自动解码响应体
- Decoder 解析响应失败(如 JSON 格式错误)→ 抛出 DecodeException
- 服务名解析失败(如 Nacos/Eureka 中找不到
userservice)→ 抛出 IllegalStateException
自定义错误解码器(核心手段)
通过实现 ErrorDecoder,把原始 HTTP 异常转化为可识别的业务异常或统一返回对象:
@Component
public class CustomErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultDecoder = new Default();
<pre class="brush:php;toolbar:false;">@Override
public Exception decode(String methodKey, Response response) {
try {
// 读取响应体(注意:只能读一次)
String body = Util.toString(response.body().asReader(StandardCharsets.UTF_8));
ObjectMapper mapper = new ObjectMapper();
if (response.status() == 404) {
return new UserNotFoundException("用户不存在:" + body);
} else if (response.status() == 500) {
Map<string object> err = mapper.readValue(body, Map.class);
return new ServiceException((String) err.get("message"));
}
} catch (IOException ignored) {}
return defaultDecoder.decode(methodKey, response);
}</string>}
再在 @FeignClient 中指定:
@FeignClient(value = "userservice",
configuration = CustomFeignConfiguration.class)
public interface UserClient { ... }
配置超时与重试策略
避免因网络抖动导致瞬时失败被误判为异常:
- 在
application.yml中设置连接与读取超时(单位:毫秒):
feign:
client:
config:
default:
connectTimeout: 3000
readTimeout: 5000
hystrix:
enabled: false # Spring Cloud 2020+ 默认禁用 Hystrix,改用 resilience4j 或自定义重试
- 启用 Feign 原生重试(需配合
Retryer):
@Configuration
public class CustomFeignConfig {
@Bean
public Retryer feignRetryer() {
// 最多重试 3 次,初始间隔 100ms,最大间隔 1s,无退避
return new Retryer.Default(100, TimeUnit.SECONDS.toMillis(1), 3);
}
}
降级 fallback 和 fallbackFactory(容错必备)
当远程服务完全不可用时,提供兜底逻辑,防止雪崩:
-
fallback:返回固定对象,无法获取原始异常信息 -
fallbackFactory:可捕获异常并做差异化处理(推荐)
@FeignClient(value = "userservice",
fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
<p>@Component
public class UserClientFallbackFactory implements FallbackFactory<userclient> {
@Override
public UserClient create(Throwable cause) {
return userId -> {
log.warn("调用 userservice 失败,userId={},原因={}", userId, cause.getMessage());
return new User().setId(-1L).setName("未知用户");
};
}
}</userclient></p>
不复杂但容易忽略:异常处理必须覆盖「能连上但业务失败」和「根本连不上」两类场景,缺一不可。











