【问题标题】:Catching exception Feign捕捉异常 Feign
【发布时间】:2020-04-20 01:52:09
【问题描述】:

我想处理来自 feign 客户端的任何异常,即使服务不可用。但是我无法使用 try/catch 捕获它们。这是我的假客户:

@FeignClient(name = "api-service", url ="localhost:8888")
public interface ClientApi extends SomeApi {

}

api 在哪里:

@Path("/")
public interface SomeApi {

  @GET
  @Path("test")
  String getValueFromApi();

}

使用 try/catch 的客户端:

@Slf4j
@Service
@AllArgsConstructor
public class SampleController implements SomeApi {

  @Autowired
  private final ClientApi clientApi;

  @Override
  public String getValueFromApi() {
    try {
      return clientApi.getValueFromApi();
    } catch (Throwable e) {
      log.error("CAN'T CATCH");
      return "";
    }
  }
}

依赖在版本中:

  • spring-boot 2.2.2.RELEASE
  • spring-cloud Hoxton.SR1

代码应该根据How to manage Feign errors?工作。

我收到了一些长堆栈跟踪,其中例外是:

  1. 原因:java.net.ConnectException:连接被拒绝(连接被拒绝)
  2. 引起:feign.RetryableException:连接被拒绝(连接被拒绝)执行GET http://localhost:8888/test
  3. 原因:com.netflix.hystrix.exception.HystrixRuntimeException:ClientApi#getValueFromApi() 失败并且没有可用的回退。

即使客户端服务(在本例中为 localhost:8888)不可用,如何正确捕获 Feign 异常?

附言。当 feign 客户端服务可用时,它可以工作,好的。我只关注异常方面。

【问题讨论】:

    标签: spring-boot spring-cloud-feign


    【解决方案1】:

    要启用断路器并配置您的应用程序以处理意外错误,您需要:

    1.- 启用断路器本身

    @SpringBootApplication
    @EnableFeignClients("com.perritotutorials.feign.client")
    @EnableCircuitBreaker
    public class FeignDemoClientApplication {
    

    2.- 创建你的后备 bean

    @Slf4j
    @Component
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public class PetAdoptionClientFallbackBean implements PetAdoptionClient {
    @Setter
        private Throwable cause;
    @Override
        public void savePet(@RequestBody Map<String, ?> pet) {
            log.error("You are on fallback interface!!! - ERROR: {}", cause);
        }
    }
    

    对于后备实施,您必须牢记以下几点:

    • 必须标记为@Component,它们在整个应用程序中是唯一的。
    • Fallback bean 应该有一个 Prototype 范围,因为我们希望为每个异常创建一个新的。
    • 使用构造函数注入进行测试。

    3.- 你的 ErrorDecoder,根据返回的 HTTP 错误实现后备启动:

    
    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 MyCustomBadRequestException();
            }
    if (response.status() >= 500) {
                return new RetryableException();
            }
            return defaultErrorDecoder.decode(methodKey, response);
        }
    }
    

    4.- 在您的配置类中,将 Retryer 和 ErrorDecoder 添加到 Spring 上下文中:

    
    @Bean
    public MyErrorDecoder myErrorDecoder() {
      return new MyErrorDecoder();
    }
    
    @Bean
    public Retryer retryer() {
        return new Retryer.Default();
    }
    

    您还可以向重试器添加自定义:

    
    class CustomRetryer implements Retryer {
    private final int maxAttempts;
        private final long backoff;
        int attempt;
    public CustomRetryer() {
            this(2000, 5); //5 times, each 2 seconds
        }
    public CustomRetryer(long backoff, int maxAttempts) {
            this.backoff = backoff;
            this.maxAttempts = maxAttempts;
            this.attempt = 1;
        }
    public void continueOrPropagate(RetryableException e) {
            if (attempt++ >= maxAttempts) {
                throw e; 
            }
    try {
                Thread.sleep(backoff);
            } catch (InterruptedException ignored) {
                Thread.currentThread().interrupt();
            }
        }
    @Override
        public Retryer clone() {
            return new CustomRetryer(backoff, maxAttempts);
        }
    }
    

    如果您想获得有关如何在应用程序中实现 Feign 的功能示例,请阅读 this 文章。

    【讨论】:

      【解决方案2】:

      处理您的服务不可用的情况的更好方法是使用断路器模式。幸运的是,使用 Netflix Hystrix 作为断路器模式的实现很容易。

      首先,你需要在应用配置中为feign客户端开启Hystrix。

      application.yml

      feign:
        hystrix:
          enabled: true
      

      那么你应该为指定的 feign 客户端接口编写一个回退类。 在这种情况下,回退类中的getValueFormApi 方法将与您编写的catch 块的行为非常相似(但当电路处于打开状态并且不会尝试原始方法时除外)。

      @Component
      public class ClientApiFallback implements ClientApi {
      
        @Override
        public String getValueFromApi(){
          return "Catch from fallback";
        }
      }
      

      最后,您只需要为您的 feign 客户端指定后备类。

      @FeignClient(name = "api-service", url ="localhost:8888", fallback = ClientApiFallback.class)
      public interface ClientApi extends SomeApi {
      
      }
      

      这样你的方法getValueFromApi 是安全的。如果, 无论出于何种原因,任何从getValueFromApi 逃逸的未捕获异常都会调用ClientApiFallback 方法。

      【讨论】:

        猜你喜欢
        • 2012-01-10
        • 1970-01-01
        • 1970-01-01
        • 2023-04-04
        • 1970-01-01
        • 1970-01-01
        • 2012-03-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多