首页 > 解决方案 > 后备工厂无法处理 Feign 客户端中的自定义异常

问题描述

我的要求是访问从第一个服务抛出的自定义异常及其在第二个服务中的正文内容

到目前为止,我已经尝试了 2 件事,FallbackFactory 和 ErrorDecoder,其中只有 Fallback factory对我有用。错误解码器没有从其他服务抛出的异常消息。这是我在另一个问题中找到的示例代码:

将有 2 种服务:库存服务和产品服务

库存服务

InventoryController.java

@RestController
@RequestMapping("/inventories")
public class InventoryController {

    private final ProductServiceClient productService;

    public InventoryController(ProductServiceClient productService) {
        super();
        this.productService = productService;
    }

    @GetMapping
    public ResponseEntity<?> companyInfo() {

        return productService.hello();

    }
}

ProductServiceClient.java

@FeignClient(name = "product-service", url = "http://localhost:9100", fallbackFactory = ProductServiceClientFallback.class)
public interface ProductServiceClient {

    @GetMapping("/products")
    ResponseEntity<?> hello();

}

@Component
class ProductServiceClientFallback implements FallbackFactory<ProductServiceClient> {

    @Override
    public ProductServiceClient create(Throwable cause) {

        return new ProductServiceClient() {

            @Override
            public ResponseEntity<?> hello() {
                System.out.println("hello!! fallback reason was " + cause.getMessage());
                return ResponseEntity.ok().build();
            }

        };
    }

}

产品服务

产品控制器.java

@RestController
@RequestMapping(value = "/products")
public class ProductController {

    @GetMapping
    public String hello() throws Exception {
        if (true) {
            throw new Exception("Service B Exception...");
        }
        return "Hello World";
    }
}

ProductControllerAdvice.java

@RestControllerAdvice
public class ProductControllerAdvice {
    @ExceptionHandler
    public ResponseEntity<?> handleException(Exception exception) {
        return new ResponseEntity<>("Caused due to : " + exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

因此,当在 Inventory 控制器中触发/inventories api 时,它会通过 Feign 客户端触发对产品服务的调用,在产品服务端,我会抛出一个带有消息的自定义异常,我必须在我的库存服务中访问该消息.

为了实现这一点,我已经实现了后备工厂,并且它在测试工作区中工作,因为我在库存服务的控制台中得到了这样的输出

hello!! fallback reason was status 500 reading ProductServiceClient#hello(); content:
Caused due to : Service B Exception...

但是,我的问题是,当我对正在处理的应用程序尝试类似的方法时,我没有收到异常消息,而是得到了这样的输出

reached fallback on workflow side, reason: status 400 reading ProvisioningServiceProxy#executeOrderAction(Long,Long,String)

服务-A

测试服务A.java

@FeignClient( url = "/executeOrder", fallbackFactory = TestServiceAFallback.class )
public interface TestServiceA extends Serializable{
    @PostMapping( value = "order/{requestId}/order/{orderId}/{command}" )
    public ResponseEntity<ProcessInstanceVariable> executeOrderAction(                                                                            @PathVariable( name = "command" ) String command );
}

引发自定义异常的Service-B

TestServiceBController.java

@PostMapping( value = /executeOrder )
public ResponseEntity<ProcessInstanceVariable> executeOrderAction(                                                                      @PathVariable( value = "command" ) String command )
{  //switch code to check the command value and throw exception for one particular command
          throw new ValidationException("validation exception from service B");
}

我也有一个建议,它处理验证异常,并且该类中有这样的方法

TestServiceBControllerAdvice.java

@ExceptionHandler( ValidationException.class )
public ResponseEntity<Object> handleValidationException( ValidationException ve )
{
    return new ResponseEntity<>( ve.getMessage(), HttpStatus.BAD_REQUEST );
}

所以,我期待在 TestServiceA 端收到我从 TestServiceB 发送的消息,但我收到一条通用消息,显示在读取 API 时 BAD REQUEST。

除了以下配置,我不确定 TestServiceA 端是否需要任何额外的配置:

testServiceA.properties

feign.hystrix.enabled=true

让我知道我是否遗漏了任何东西,我已经阅读了这个文档,在我看来,我已经按照它应该发生的方式完成了实现,以获取从其他服务抛出的消息和异常主体。

标签: javaspringspring-bootfeign

解决方案


对于任何来这个问题寻找答案的人,我最终都实现了 ErrorDecoder,它帮助我捕获了错误。细节对我来说有点褪色,消息是如何被捕获的。但我使用了以下代码:

public class CustomExceptionDecoder implements feign.codec.ErrorDecoder
{
    @Override
    public Exception decode( String methodKey,
                             Response response )
    {
        final ErrorDecoder defaultErrorDecoder = new Default();
        try
        {
            if( response.body() != null )
            {
                byte[] bodyData = Util.toByteArray( response.body().asInputStream() );
                String responseBody = new String( bodyData );
                LOGGER.error( "Error captured in Custom Exception Decoder: ", responseBody );
                return new CustomValidationException( responseBody );
            }
        }
        catch( IOException e )
        {
            LOGGER.error( "Throwing IOException :: {}", e.getCause() );
        }
        return defaultErrorDecoder.decode( methodKey, response );
    }
}

推荐阅读