Spring Feign客户端异常处理

26

我有一些Feign客户端向其他微服务发送请求。

@FeignClient(name="userservice")
public interface UserClient {

    @RequestMapping(
        method= RequestMethod.GET,
        path = "/userlist"
    )
    String getUserByid(@RequestParam(value ="id") String id);
}

现在我以这种方式发送请求

try {
    String responseData = userClient.getUserByid(id);
    return responseData;
} catch(FeignException e) {
    logger.error("Failed to get user", id);
} catch (Exception e) {
    logger.error("Failed to get user", id);
}

问题在于,如果出现任何FeignException,我没有收到任何错误代码。

我需要发送相应的错误代码到其他API以发送给调用者。

那么如何提取错误代码?我想提取错误代码并构建一个responseEntity

我得到了这个代码,但不知道如何在我的函数中使用它。

6个回答

27

虽然我来晚了,但这是我的两分钱。我们有相同的用例,需要根据错误代码处理异常,我们使用了自定义ErrorDecoder

public class CustomErrorDecoder implements ErrorDecoder {

    @Override
    public Exception decode(String methodKey, Response response) {
        String requestUrl = response.request().url();
        Response.Body responseBody = response.body();
        HttpStatus responseStatus = HttpStatus.valueOf(response.status());

        if (responseStatus.is5xxServerError()) {
            return new RestApiServerException(requestUrl, responseBody);
        } else if (responseStatus.is4xxClientError()) {
            return new RestApiClientException(requestUrl, responseBody);
        } else {
            return new Exception("Generic exception");
        }
    }
}
FeignClientConfiguration类中返回上述类的@Bean
public class MyFeignClientConfiguration {

    @Bean
    public ErrorDecoder errorDecoder() {
        return new CustomErrorDecoder();
    }
}

使用此作为FeignClient的配置类。

@FeignClient(
    value = "myFeignClient", 
    configuration = MyFeignClientConfiguration.class
)

然后您可以使用 GlobalExceptionHandler 处理这些异常。


4
你可以删除 MyFeignClientConfiguration 上的 @Configuration,因为该类是通过 configuration = MyFeignClientConfiguration.class 实例化的。 - Stanislas Klukowski

15

虽然不是同一个问题,但这个回答帮助了我。

OpenFeign的FeignException没有绑定到特定的HTTP状态(即没有使用Spring的@ResponseStatus注释),这使得当出现FeignException时,Spring会默认为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返回与您收到的相同的HTTP状态


你是如何配置的?这个对我不起作用。我需要添加额外的代码来配置它吗? - Stevers
除了2xx HTTP状态码外,我应该如何返回FeignException的响应主体? - Juan Rojas
这是唯一对我有效的答案,谢谢! - nelsw
6
虽然这个方法有效,但它促进了一种不好的实践,即将低级别的实现细节泄露到堆栈中。FeignClient在应用程序层中使用,为域提供一些价值,与传输层(即控制器、HTTP状态等)没有任何关系。如果你想走这条路,我建议先将“FeignException”包装成一个适当的域异常,例如“UserNotFoundException”,然后才在后者上使用你提出的方法。 - th3n3rd
如上面的评论所提到的,需要提供缺失的信息。此外,Feign客户端基本上与传输层无关。 - Omar El Hussein

6

你尝试在你的Feign客户端上实现FallbackFactory了吗?

https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html#spring-cloud-feign-hystrix-fallback

在create方法中,在返回前,您可以使用以下代码片段检索http状态码:
String httpStatus = cause instanceof FeignException ? Integer.toString(((FeignException) cause).status()) : "";

例子:

    @FeignClient(name="userservice", fallbackFactory = UserClientFallbackFactory.class)
    public interface UserClient {
    
        @RequestMapping(
                method= RequestMethod.GET,
                          path = "/userlist")
        String getUserByid(@RequestParam(value ="id") String id);
    
    }
    
    
    @Component
    static class UserClientFallbackFactory implements FallbackFactory<UserClient> {
        @Override
        public UserClient create(Throwable cause) {
    
         String httpStatus = cause instanceof FeignException ? Integer.toString(((FeignException) cause).status()) : "";
    
         return new UserClient() {
            @Override
            public String getUserByid() {
                logger.error(httpStatus);
                // what you want to answer back (logger, exception catch by a ControllerAdvice, etc)
            }
        };
    }
   }

3
控制器通知无法捕获由后备工厂抛出的异常。 - KnockKnock
2
一般情况下,控制器中会有多个方法,那么即使我只想处理其中的几个方法,我们是否要实现每个方法? - Akki

4
我可能有些晚了,但我想确保你也查看其他潜在的解决方案。
Spring中的Feign处理异常的方式非常糟糕,因此我提出了一个自定义解决方案,创建了这个强大的环境来定义您想要的业务异常。
它利用自定义的ErrorDecoder注册到Feign客户端,并增加了根据方法或类级别自定义异常处理的可能性。
看一下:使用Feign客户端实现可维护的错误处理?不再是梦想

非常感谢您的回答,但如果您能在这里发布答案而不是链接,那就太完美了! - ikhvjs

0

文档中提到了一个名为 ErrorDecored 的接口,用于解决这个问题,详情请查看文档

上面关于 FallbackFactory 的答案是可行的,但是落入了其他抽象层。


2
该URL只是通往一般的Feign客户端文档,不包括异常处理。 - nelsw

0

String errorJson = e.contentUTF8();

HttpStatus status = HttpStatus.valueOf(e.status());

Map<String, List> errorResponse = objectMapper.readValue(errorJson, Map.class);

return new ResponseDTO(status,e.status(), errorResponse.get("errors").get(0));


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接