Incompatible FallbackFactory Instance Exception

2 분 소요


RECOMMEND POSTS BEFORE THIS

1. Occurred Exception

Spring Cloud Netflix Hystrix 포스트를 작성하면서 다음과 같은 예외(exception)를 만났습니다.

Caused by: java.lang.IllegalStateException: Incompatible fallbackFactory instance. Fallback/fallbackFactory of type class cloud.in.action.proxy.BlogClientFallbackFactory is not assignable to interface org.springframework.cloud.openfeign.FallbackFactory for feign client blog-client
    at org.springframework.cloud.openfeign.FeignCircuitBreakerTargeter.getFromContext(FeignCircuitBreakerTargeter.java:83) ~[spring-cloud-openfeign-core-2.2.7.RELEASE.jar:2.2.7.RELEASE]
    at org.springframework.cloud.openfeign.FeignCircuitBreakerTargeter.targetWithFallbackFactory(FeignCircuitBreakerTargeter.java:58) ~[spring-cloud-openfeign-core-2.2.7.RELEASE.jar:2.2.7.RELEASE]
    at org.springframework.cloud.openfeign.FeignCircuitBreakerTargeter.target(FeignCircuitBreakerTargeter.java:49) ~[spring-cloud-openfeign-core-2.2.7.RELEASE.jar:2.2.7.RELEASE]
    at org.springframework.cloud.openfeign.FeignClientFactoryBean.getTarget(FeignClientFactoryBean.java:391) ~[spring-cloud-openfeign-core-2.2.7.RELEASE.jar:2.2.7.RELEASE]
    at org.springframework.cloud.openfeign.FeignClientFactoryBean.getObject(FeignClientFactoryBean.java:347) ~[spring-cloud-openfeign-core-2.2.7.RELEASE.jar:2.2.7.RELEASE]
    at org.springframework.cloud.openfeign.FeignClientsRegistrar.lambda$registerFeignClient$0(FeignClientsRegistrar.java:240) ~[spring-cloud-openfeign-core-2.2.7.RELEASE.jar:2.2.7.RELEASE]
  ...

로그 내용을 살펴보면 BlogClientFallbackFactory 객체를 org.springframework.cloud.openfeign.FallbackFactory 인터페이스에 할당할 수 없다는 메시지를 볼 수 있습니다. 이번 포스트에선 해당 예외가 발생한 원인을 분석해보고, 이를 해결하는 방법에 대해 정리하였습니다.

2. Problem Analysis

2.1. Wrong Usage

설정에 맞지 않은 팩토리(factory) 클래스를 사용하면 문제가 발생합니다. 다음과 같은 설정과 인터페이스를 함께 사용하면 에러가 발생합니다.

  • feign.circuitbreaker.enabled 설정
  • feign.hystrix.FallbackFactory 인터페이스

2.1.1. application.yml

  • feign.circuitbreaker.enabled=true 설정을 사용합니다.
feign:
  circuitbreaker:
    enabled: true
  client:
    config:
      default:
        connect-timeout: 5000
        read-timeout: 5000

2.1.2. BlogClientFallbackFactory Class

  • feign.hystrix.FallbackFactory 인터페이스를 확장한 팩토리 클래스를 만들어 사용합니다.
package cloud.in.action.proxy;

import feign.hystrix.FallbackFactory;
import lombok.extern.log4j.Log4j2;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(
        name = "blog-client",
        url = "http://b-service:8080",
        fallbackFactory = BlogClientFallbackFactory.class
)
public interface BlogClient {

    @GetMapping(path = "/timeout")
    String requestWithTimeoutException();

    @GetMapping(path = "/exception")
    String requestWithIntentionalException();
}

@Log4j2
@Component
class BlogClientFallbackFactory implements FallbackFactory<BlogClient> {

    @Override
    public BlogClient create(Throwable cause) {
        log.error(cause.getMessage(), cause);
        return new BlogClientFallbackPlan();
    }

    class BlogClientFallbackPlan implements BlogClient {

        @Override
        public String requestWithTimeoutException() {
            return "timeout fallback";
        }

        @Override
        public String requestWithIntentionalException() {
            return "implicit exception fallback";
        }
    }
}

2.2. Solve the problem

circuitbreaker 설정은 내부적으로 org.springframework.cloud.openfeign.FallbackFactory 인터페이스를 상속한 팩토리 클래스를 사용하도록 구현되어 있습니다. 적절한 인터페이스를 사용하도록 코드를 변경합니다.

  • org.springframework.cloud.openfeign.FallbackFactory 인터페이스를 사용하도록 코드를 변경합니다.
package cloud.in.action.proxy;

import lombok.extern.log4j.Log4j2;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;

@FeignClient(
        name = "blog-client",
        url = "http://b-service:8080",
        fallbackFactory = BlogClientFallbackFactory.class
)
public interface BlogClient {

    @GetMapping(path = "/timeout")
    String requestWithTimeoutException();

    @GetMapping(path = "/exception")
    String requestWithIntentionalException();
}

@Log4j2
@Component
class BlogClientFallbackFactory implements FallbackFactory<BlogClient> {

    @Override
    public BlogClient create(Throwable cause) {
        log.error(cause.getMessage(), cause);
        return new BlogClientFallbackPlan();
    }

    class BlogClientFallbackPlan implements BlogClient {

        @Override
        public String requestWithTimeoutException() {
            return "timeout fallback";
        }

        @Override
        public String requestWithIntentionalException() {
            return "implicit exception fallback";
        }
    }
}

3. Issue Report

Github에 관련된 질문을 올리니 다음과 같은 답변을 얻을 수 있었습니다.

feign.circuitbreaker.* is for enabling support for Spring Cloud CircuitBreaker. It does not use Hystrix. You should use Spring Cloud CircuitBreaker as Hystrix is removed in the 2020.0.x release.

2020.0.x 릴리즈부터 Spring Cloud CircuitBreaker로 Hystrix를 대체한다고 합니다. feign.circuitbreaker.* 설정을 통해 Spring Cloud CircuitBreaker 지원을 활성화하라고 합니다.

Question

Answer

TEST CODE REPOSITORY

RECOMMEND NEXT POSTS

CLOSING

Spring Cloud Netflix Hystrix 포스트를 재작성하면서 불필요한 코드나 의존성을 제거하다보니 추가적으로 몇 가지 사실을 발견했습니다.

  • spring-cloud-starter-netflix-eureka-client 의존성을 사용하면 해당 문제가 발생합니다.
    • Spring Cloud CircuitBreaker 의존성은 spring-cloud-starter-netflix-eureka-client을 통해 적용됩니다.
  • spring-cloud-starter-netflix-eureka-client 의존성을 사용하지 않으면 feign.hystrix.enabled 설정을 사용합니다.
    • OpenFeign 의존성만 사용하는 경우 feign.hystrix.enabled 설정으로 회로 차단기를 활성화시킵니다.
    • feign.hystrix.FallbackFactory로 팩토리를 사용합니다.

댓글남기기