OpenFeign 源码阅读 —— 响应解码与错误处理
下游返回的 HTTP 响应如何变成方法返回值?错误如何映射为异常?本文从源码拆解 Decoder 解码器链、ErrorDecoder 错误映射,以及异步调用的适配。
响应处理整体流程
client.execute() 返回 Response(状态码 + 头 + 体)
│
▼
executeAndDecode()
├─ 2xx → Decoder.decode() → 方法返回类型
├─ 404(decode404=true)→ Decoder.decode() → null/空
└─ 其他错误码 → ErrorDecoder.decode() → 抛 FeignException/业务异常Decoder:响应解码器
接口
java
// feign.codec.Decoder
public interface Decoder {
Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;
// 默认实现:只支持 byte[] / String / InputStream
class Default implements Decoder {
@Override
public Object decode(Response response, Type type) throws IOException {
// 根据返回类型取响应体
if (response.body() == null) return null;
if (byte[].class.equals(type)) {
return Util.toByteArray(response.body().asInputStream());
} else if (String.class.equals(type)) {
return Util.toString(response.body().asReader());
} else if (InputStream.class.equals(type)) {
return response.body().asInputStream();
} else {
throw new DecodeException("不支持的返回类型: " + type);
}
}
}
}SpringDecoder(默认)
java
// spring-cloud-openfeign-core
public class SpringDecoder implements Decoder {
private final OptionalDecoder delegate;
public SpringDecoder() {
// 集成 Spring 的 HttpMessageConverter(默认 Jackson)
this.delegate = new OptionalDecoder(
new ResponseEntityDecoder(new SpringDecoder(this)));
}
@Override
public Object decode(Response response, Type type) throws IOException, FeignException {
// 委托 Spring 消息转换器反序列化
return delegate.decode(response, type);
}
}响应解码时序
Response(JSON body)
│
▼
OptionalDecoder
│ 检查返回类型
│
▼
ResponseEntityDecoder
│ 若返回类型是 ResponseEntity<T> → 解包
│
▼
SpringDecoder.decode()
│ 用 HttpMessageConverter(Jackson)反序列化
│
▼
方法返回对象返回类型处理
| 返回类型 | 解码行为 |
|---|---|
| Order(普通对象) | Jackson 反序列化为 Order |
List<Order> | 反序列化为泛型列表 |
ResponseEntity<Order> | 包装状态码 + 头 + 体 |
| String / byte[] | 原样读取 |
| void | 忽略响应体 |
Optional<T> | 支持(空体 → empty) |
泛型擦除问题
java
// 为什么 Feign 能正确反序列化 List<Order>?
// MethodMetadata 保存了 returnTypeClass(含泛型参数 Type),
// decode 时传给 Jackson,保留泛型信息(TypeReference 机制)。ErrorDecoder:错误映射
接口
java
// feign.codec.ErrorDecoder
public interface ErrorDecoder {
Exception decode(String methodKey, Response response);
// 默认实现
class Default implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
// 根据状态码生成不同异常
FeignException exception = errorStatus(methodKey, response);
// 可重试状态(429/5xx)→ RetryableException
if (isRetryable(response.status())) {
return new RetryableException(...);
}
return exception;
}
}
}默认异常映射
| 状态码 | 异常 |
|---|---|
| 400 | FeignException.BadRequest |
| 401 | FeignException.Unauthorized |
| 404 | FeignException.NotFound |
| 405 | FeignException.MethodNotAllowed |
| 429 | FeignException.TooManyRequests(Retryable) |
| 5xx | FeignException(ServerError,Retryable) |
| 其他 | FeignException |
java
// feign.FeignException 子类
public static class BadRequest extends FeignException { ... }
public static class Unauthorized extends FeignException { ... }
public static class NotFound extends FeignException { ... }
public static class TooManyRequests extends FeignException { ... }executeAndDecode:完整源码
java
// SynchronousMethodHandler.executeAndDecode
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = targetRequest(template);
long start = System.nanoTime();
Response response;
// 重试循环
while (true) {
try {
response = client.execute(request, options); // 执行请求
} catch (IOException e) {
throw new RetryableException(...); // IO 异常 → 可重试
}
// 响应处理
if (response.status() >= 200 && response.status() < 300) {
// 成功:
if (isVoidType(metadata.returnType())) {
return null; // void 返回
} else if (isResponseType(metadata.returnType())) {
return response; // 返回 Response
} else {
return decodeResponse(response, metadata.returnType()); // 解码
}
}
// 404 + decode404 配置 → 尝试解码(可能返回 null)
if (response.status() == 404 && decode404) {
return decodeResponse(response, metadata.returnType());
}
// 错误状态 → ErrorDecoder
Exception exception = errorDecoder.decode(metadata.configKey(), response);
if (exception instanceof RetryableException) {
// 可重试 → 重试器决定
Retryer.Result result = retryer.continueOrPropagate((RetryableException) exception);
if (result == null) throw exception;
request = result.getRequest(); // 换新请求重试
continue;
}
throw exception; // 不可重试 → 直接抛
}
}重试与错误处理分支
响应状态码
├─ 2xx → 解码返回
├─ 404 + decode404 → 解码返回(null)
├─ RetryableException(429/5xx/IO)→ Retryer 重试(默认不重试)
└─ 其他 → ErrorDecoder 抛异常自定义 ErrorDecoder 实战
把业务错误码映射为业务异常
java
public class BusinessErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultDecoder = new Default();
@Override
public Exception decode(String methodKey, Response response) {
// 1. 尝试解析响应体 JSON
try {
String body = Util.toString(response.body().asReader());
ErrorResult result = new ObjectMapper().readValue(body, ErrorResult.class);
// 2. 业务错误码 → 自定义异常
if (result.getCode() == 1001) {
return new OrderNotFoundException(result.getMessage());
}
if (result.getCode() == 1002) {
return new StockShortageException(result.getMessage());
}
} catch (Exception ignored) { }
// 3. 其他情况走默认映射
return defaultDecoder.decode(methodKey, response);
}
}java
// 装配
@Configuration
public class OrderClientConfig {
@Bean
public ErrorDecoder errorDecoder() {
return new BusinessErrorDecoder();
}
}java
// 使用
try {
orderClient.createOrder(req);
} catch (StockShortageException e) {
// 业务降级
}异步适配(AsyncFeign)
异步返回类型
java
@FeignClient(name = "order-service")
public interface OrderClient {
@GetMapping("/api/order/{id}")
CompletableFuture<Order> getOrder(@PathVariable("id") Long id); // 异步
}AsyncFeign 的实现
java
// feign.AsyncFeign
public class AsyncFeign extends Feign {
// 与 ReflectiveFeign 类似,但:
// 1. 方法解析时检测返回类型是否为 CompletableFuture
// 2. SynchronousMethodHandler 执行后把结果包装为 CompletableFuture
// 3. 实际调用在后台线程池执行(不阻塞调用方)
}调用 asyncGetOrder(id)
│
├─ 返回 CompletableFuture(立即返回,不阻塞)
│
└─ 后台线程:编码 → 发送 → 解码 → 完成 Future异步与降级
Spring Cloud 中异步 + Sentinel/Hystrix 降级时,返回类型必须是 CompletableFuture,降级逻辑在 Future 完成时触发。
常见问题
- 下游返回 500 但没抛异常? ErrorDecoder 默认对 5xx 抛 ServerError;如果配置了 ignore 或降级,异常被吞掉。
- 返回 List 为空列表还是 null? 下游返回空 JSON
[]→ 空列表;返回空体 → null(视解码结果)。 - decode404 有什么用? 开启后 404 也走解码(适合查询接口返回 null 而非异常)。
- 自定义 ErrorDecoder 不生效? 确认 Bean 是否注册到对应 Feign 的 configuration(全局/局部作用域)。