2022 年关于 Spring Cloud 服务间调用组件 Feign 配置的总结。 Maven 依赖: 一般来说,我们服务接口响应内容都有一层全局的包装,比如: 在服务间调用时如果在每个 Feign 的接口处定义同样的类比如 使用方式: Feign 在收到响应 HTTP 状态码为 40X、50X 等时会进入 前文提到, 至此,框架自动转交上游服务的业务异常,业务中不必关注全局包装的拆解问题,完美!<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
1. 全局响应包装的拆解
{
"code": 0,
"message": "成功",
"data": {
...
}
}
Result<XxVO>
再 getData()
不是不行,但是比较繁琐,而且在获取想要的数据前可能需要根据 code
、message
判断得到的结果是否正确。通过自定义 decoder
可以比较好地解决前一个问题:// 注入 Jackson 的 ObjectMapper,如果不喜欢 Lombok 可以自行修改
@RequiredArgsConstructor
public class UnwrapDecoder implements Decoder {
private final ObjectMapper objectMapper;
@SneakyThrows
@Override
public Object decode(Response response, Type type) {
Reader reader = response.body().asReader(Charset.defaultCharset());
Result<?> result = objectMapper.readValue(reader, Result.class);
// 根据 code 判断操作是否成功
if (ResultCode.isSuccess(result.getCode())) {
Object data = result.getData();
JavaType javaType = TypeFactory.defaultInstance().constructType(type);
return objectMapper.convertValue(data, javaType);
}
// 若不成功,抛出业务异常,注意此处的异常会在 DecodeException 中被捕获,后文会处理
throw new BusinessException(result.getCode(), result.getMessage());
}
}
@FeignClient(name = "serviceA", configuration = {UnwrapDecoder.class})
...
@GetMapping("/xx")
// 无需使用 Result<XxVO> 泛型包装,业务中直接调用
XxVO getXxVO();
...
2. 上游异常请求统一处理
ErrorDecoder
,我们可以自定义处理器:@Configuration
@RequiredArgsConstructor
@Slf4j
public class FeignClientErrorDecoder implements ErrorDecoder {
private final ObjectMapper objectMapper;
@Override
public Exception decode(String methodKey, Response response) {
try {
Reader reader = response.body().asReader(Charset.defaultCharset());
Result<?> result = objectMapper.readValue(reader, Result.class);
// 如果上游服务响应符合全局包装约定,再次抛出即可
return new BusinessException(result.getCode(), result.getMessage());
} catch (Exception e) {
log.error("Response 转换异常: ", e);
return new BusinessException(ResultCode.INTERFACE_INNER_INVOKE_ERROR);
}
}
}
3. 关于 Feign 的全局异常处理
UnwrapDecoder
中抛出的业务异常会被捕获并以 DecodeException 抛出,因此在全局异常中单独处理(此类可以与常规的全局异常处理类分开共存):@RestControllerAdvice
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE) // 优先级
@ResponseStatus(code = HttpStatus.BAD_REQUEST) // 统一 HTTP 状态码
@RequiredArgsConstructor
public class FeignExceptionHandler {
/**
* 拦截 FeignException 异常,Jackson 处理失败等情况会进入
*/
@ExceptionHandler(FeignException.class)
public Result<?> handleFeignException(FeignException e) {
log.error("FeignException: ", e);
return Result.errorResult(ResultCode.INTERFACE_INNER_INVOKE_ERROR, e.getMessage());
}
/**
* 拦截 DecodeException 异常,decoder 中抛出的自定义全局异常会进入此处
*/
@ExceptionHandler(DecodeException.class)
public Result<?> handleDecodeException(DecodeException e) {
Throwable cause = e.getCause();
if (cause instanceof BusinessException) {
BusinessException businessException = (BusinessException) cause;
// 上游符合全局响应包装约定的再次抛出即可
return Result.errorResult(businessException.getCode(), businessException.getMessage());
}
log.error("DecodeException: ", e);
return Result.errorResult(ResultCode.INTERFACE_INNER_INVOKE_ERROR, e.getMessage());
}
}
· 转载请注明 https://kytrun.com/spring-cloud-feign-unwrap-and-exception/