26

我正在使用Netflix Feign将微服务 A 的一个操作调用到微服务 B 的其他其他操作,该微服务 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{\r\n  \"errorCode\" : \"VALIDATION_EXISTS\",\r\n  \"errorMessage\" : \"Code already exists.\"\r\n}",
  "path": "/code/validate"
}

但是我需要返回和微服务操作B一样的。

使用 Netflix Feign 通过微服务传播状态和异常的最佳方式或技术是什么?

4

6 回答 6

25

你可以使用假装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();
}
于 2016-08-05T13:43:27.487 回答
4

我做了一个小库的无耻插件,它使用反射根据响应正文中返回的错误代码动态重新抛出已检查的异常(如果它们在 Feign 接口上则未检查)。

有关自述文件的更多信息: https ://github.com/coveo/feign-error-decoder

于 2016-12-14T19:12:07.153 回答
4

OpenFeign 的FeignException不绑定到特定的 HTTP 状态(即不使用 Spring 的@ResponseStatus注解),这使得 Spring500在遇到FeignException. 没关系,因为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 返回您从微服务 B 收到的相同 HTTP 状态。您可以更进一步,还返回原始响应正文:

response.getOutputStream().write(e.content());
于 2019-05-20T13:39:23.637 回答
1

编写您的自定义异常映射器并注册它。您可以自定义响应。

完整的例子在这里

public class GenericExceptionMapper implements ExceptionMapper<Throwable> {

    @Override
    public Response toResponse(Throwable ex) {
        return Response.status(500).entity(YOUR_RETURN_OBJ_HERE).build();
    }

}
于 2016-08-05T13:48:14.047 回答
1

自 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

免责声明:我是 feign 的贡献者,也是该错误解码器的主要开发者。

于 2019-03-01T22:32:07.990 回答
0

我们所做的如下:

与两个微服务共享包含异常的公共 jar。

1.) 在微服务中,将异常转换为 DTO 类可以说是 ErrorInfo。它将包含您的自定义异常的所有属性,其中包含异常类型名称,其中将包含异常类名称。

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);
 }
于 2018-01-24T15:29:54.667 回答