FeignClient의 커스텀 Decoder가 빈으로 등록되지 않을 때

공병주(Chris)·2023년 9월 25일
0
post-thumbnail

Dandi에는 2개의 다른 API를 호출해야하는 상황이 있었습니다.
따라서, 2개의 FeignClient를 등록해서 사용하는 중이었습니다.
버전은 spring-cloud-openfeign:3.1.5 입니다.

@FeignClient(name = "${feign.client.apple.name}", url = "${feign.client.apple.url}")
public interface AppleApiCaller {

    @GetMapping
    ApplePublicKeys getPublicKeys();
}
@FeignClient(name = "feign.client.kma.name", url = "feign.client.kma.url",
        configuration = KmaWeatherFeignConfiguration.class)
public interface KmaWeatherApiCaller {

    @GetMapping
    WeatherResponses getWeathers(@SpringQueryMap(encoded = true) WeatherRequest weatherRequest);
}

Decoder는 기본적으로 등록되는 OptionalDecoder를 사용하고 있었습니다.

@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {
    // ...
	@Bean
	@ConditionalOnMissingBean
	public Decoder feignDecoder(ObjectProvider<HttpMessageConverterCustomizer> customizers) {
		return new OptionalDecoder(new ResponseEntityDecoder(new SpringDecoder(messageConverters, customizers)));
	}
    // ...
}

요구사항

위 Decoder 들 중에 KmaWeatherApiCaller의 Decoder를 커스터마이징 해야하는 상황이 생겼고, 아래와 같이 구성을 했습니다. kmaWeatherApiCaller의 Decoder만 커스터마이징을 하고 다른 AppleApiCaller는 위의 Feign 기본 Decoder를 사용하도록 의도했습니다.

코드를 보시기 전에 Decoder를 Bean으로 등록하는 설정파일에 @Configuration 을 사용하지 않은 이유는 @Configuration으로 Decoder를 @Bean으로 등록할 시에, 해당 Decoder가 모든 Feign에 Default로 잡히기 때문입니다. @Configuration을 사용하면 해당 Decoder가 모든 FeignClient에 적용됩니다.

Spring Cloud Open Feign 공식문서를 살펴보시면 위 사항에 대한 언급이 있습니다.

FooConfiguration does not need to be annotated with @Configuration. However, if it is, then take care to exclude it from any @ComponentScan that would otherwise include this configuration as it will become the default source for feign.Decoder, feign.Encoder, feign.Contract, etc., when specified. This can be avoided by putting it in a separate, non-overlapping package from any @ComponentScan or @SpringBootApplication, or it can be explicitly excluded in @ComponentScan.

FeignClient 설정

@FeignClient(name = "feign.client.kma.name", url = "feign.client.kma.url",
        configuration = KmaWeatherFeignConfiguration.class)
public interface KmaWeatherApiCaller {

    @GetMapping
    WeatherResponses getWeathers(@SpringQueryMap(encoded = true) WeatherRequest weatherRequest);
}
public class KmaWeatherFeignConfiguration {

    @Bean
    Decoder kmaDecoder(ObjectFactory<HttpMessageConverters> messageConverters,
                             ObjectProvider<HttpMessageConverterCustomizer> customizers) {
        return new XmlToJsonDecoder(messageConverters, customizers);
    }
}
public class XmlToJsonDecoder implements Decoder {

    private static final Logger logger = LoggerFactory.getLogger(XmlToJsonDecoder.class);
    private static final String CONTENT_TYPE = "content-type";
    private static final String TEXT_XML = "text/xml";
    private static final String HTTP_ROUTING_ERROR = "HTTP ROUTING ERROR";
    private static final WeatherResponses HTTP_ROUTING_ERROR_WEATHER_RESPONSES = new WeatherResponses(
            new WeatherResponse(new WeatherResponseHeader("100", "HTTP_ROUTING_ERROR"), null));

    private final Decoder delegate;

    public XmlToJsonDecoder(ObjectFactory<HttpMessageConverters> messageConverters,
                            ObjectProvider<HttpMessageConverterCustomizer> customizers) {
        delegate = new OptionalDecoder(new ResponseEntityDecoder(
                new SpringDecoder(messageConverters, customizers)));
        System.out.println("kmaDecoder");
    }

    @Override
    public Object decode(Response response, Type type) throws IOException, FeignException {
        if (hasXmlResponseBody(response)) {
            return decodeXmlResponseBody(response);
        }
        return delegate.decode(response, type);
    }
}

문제

해당 Decoder가 동작하는지 확인하려고 BreakPoint를 찍어보고 테스트를 돌려본 결과, 해당 Decoder가 동작하지 않고 Bean으로 등록되지 않는 것을 확인했습니다.

해결방안

디버깅도 많이하고 stackoverflow에 글도 올려보고 구글링을 엄청 한참을 헤맸는데, 해결방안은 꽤 간단했습니다.

@Configuration
@EnableFeignClients(basePackageClasses = DandiApplication.class)
public class FeignConfiguration {
}

위처럼 basePackageClasses를 Main Class로 잡아서 FeingClient를 Configure하도록 하는 방식에서
아래와 같이 client의 클라이언트를 직접 지정해주는 방식으로 변경하니 해결되었습니다.

@Configuration
@EnableFeignClients(clients = {AppleApiCaller.class, KmaWeatherApiCaller.class})
public class FeignConfiguration {
}

사실, 구체적인 이유는 모르겠습니다. @EnableFeignClients 어노테이션 쪽에 문제가 있는지 의심이 되었습니다. 살펴보면서 attribute 값들을 보고 basePackage를 통해 scanning이 아닌, 직접 Client를 지정하는 방식으로 하면 어떤지 테스트를 해보다가 얻어 걸린 결과입니다..!

구체적인 원인은 제시 못해드려도, 저와 같은 문제를 겪는 다른 분들이 시간낭비를 하지 않지 않으셨으면 좋겠다는 마음에 급히 글을 씁니다.

  • how to override feign Decoder
  • feign custom Decoder
  • feign 커스텀 Decoder
  • feign Decoder 커스터마이징

1개의 댓글

comment-user-thumbnail
2024년 10월 8일

매우 도움이 되었습니다

답글 달기