我使用Netflix Feign调用微服务a的一个操作到微服务B的另一个操作,后者使用Spring Boot验证代码。
微服务B的操作在验证不正确的情况下引发异常。然后我在微服务中处理并返回HttpStatus.UNPROCESSABLE_ENTITY
(422(,如下所示:
@ExceptionHandler({
ValidateException.class
})
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
@ResponseBody
public Object validationException(final HttpServletRequest request, final validateException exception) {
log.error(exception.getMessage(), exception);
error.setErrorMessage(exception.getMessage());
error.setErrorCode(exception.getCode().toString());
return error;
}
因此,当微服务A在下一个接口中调用B时:
@Headers("Content-Type: " + MediaType.APPLICATION_JSON_UTF8_VALUE)
@RequestLine("GET /other")
void otherOperation(@Param("other") String other );
@Headers("Content-Type: " + MediaType.APPLICATION_JSON_UTF8_VALUE)
@RequestLine("GET /code/validate")
Boolean validate(@Param("prefix") String prefix);
static PromotionClient connect() {
return Feign.builder()
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(PromotionClient.class, Urls.SERVICE_URL.toString());
}
如果验证失败,则返回内部错误500并显示下一条消息:
{
"timestamp": "2016-08-05T09:17:49.939+0000",
"status": 500,
"error": "Internal Server Error",
"exception": "feign.FeignException",
"message": "status 422 reading Client#validate(String); content:n{rn "errorCode" : "VALIDATION_EXISTS",rn "errorMessage" : "Code already exists."rn}",
"path": "/code/validate"
}
但我需要返回与微服务操作相同的B.
使用Netflix Feign通过微服务传播状态和异常的最佳方式或技术是什么?
您可以使用外部ErrorDecoder
https://github.com/OpenFeign/feign/wiki/Custom-error-handling
以下是的示例
public class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
@Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
return new MyBadRequestException();
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
为了让spring拿起ErrorDecoder,你必须把它放在ApplicationContext:上
@Bean
public MyErrorDecoder myErrorDecoder() {
return new MyErrorDecoder();
}
我做的一个小库的无耻插件,它使用反射根据响应主体中返回的错误代码动态重新抛出已检查的异常(如果它们在Feign接口上,则为未检查的异常(。
有关自述文件的更多信息:https://github.com/coveo/feign-error-decoder
OpenFeign的FeignException不绑定到特定的HTTP状态(即不使用Spring的@ResponseStatus
注释(,这使得Spring在遇到FeignException
时默认为500
。这没关系,因为FeignException
可能有许多与特定HTTP状态无关的原因。
但是,您可以更改Spring处理FeignExceptions
的方式。只需定义一个ExceptionHandler
,以您需要的方式处理FeignException
(请参阅此处(:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(FeignException.class)
public String handleFeignStatusException(FeignException e, HttpServletResponse response) {
response.setStatus(e.status());
return "feignError";
}
}
此示例使Spring返回与您从Microservice B收到的HTTP状态相同的HTTP状态。您可以更进一步,还可以返回原始响应体:
response.getOutputStream().write(e.content());
编写自定义异常映射器并注册它。您可以自定义响应。
完整的例子在这里
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
@Override
public Response toResponse(Throwable ex) {
return Response.status(500).entity(YOUR_RETURN_OBJ_HERE).build();
}
}
自2017年以来,我们创建了一个库,通过注释实现这一点(与请求等类似,通过注释进行编码非常容易(。
它基本上允许您对错误处理进行如下编码:
@ErrorHandling(codeSpecific =
{
@ErrorCodes( codes = {401}, generate = UnAuthorizedException.class),
@ErrorCodes( codes = {403}, generate = ForbiddenException.class),
@ErrorCodes( codes = {404}, generate = UnknownItemException.class),
},
defaultException = ClassLevelDefaultException.class
)
interface GitHub {
@ErrorHandling(codeSpecific =
{
@ErrorCodes( codes = {404}, generate = NonExistentRepoException.class),
@ErrorCodes( codes = {502, 503, 504}, generate = RetryAfterCertainTimeException.class),
},
defaultException = FailedToGetContributorsException.class
)
@RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
}
您可以在OpenFeign组织中找到它:https://github.com/OpenFeign/feign-annotation-error-decoder
免责声明:我是foreign的贡献者,也是错误解码器的主要开发者。
我们所做的如下:
与两个微服务共享包含异常的公共jar。
1.(在微服务中,将异常转换为DTO类,比如ErrorInfo。它将包含带有StringexceptionType的自定义异常的所有属性,该类型将包含异常类名。
2.(当在微服务B接收到它时,它将由微服务B中的ErrorDecoder处理,它将尝试从exceptionType创建一个异常对象,如下所示:
@Override
public Exception decode(String methodKey, Response response) {
ErrorInfo errorInfo = objectMapper.readValue(details, ErrorInfo.class);
Class exceptionClass;
Exception decodedException;
try {
exceptionClass = Class.forName(errorInfo.getExceptionType());
decodedException = (Exception) exceptionClass.newInstance();
return decodedException;
}
catch (ClassNotFoundException e) {
return new PlatformExecutionException(details, errorInfo);
}
return defaultErrorDecoder.decode(methodKey, response);
}