Spring Cloudにおける複数のFeignClientが同一サービスを呼び出す際のカスタム設定の問題

@FeignClientアノテーションには、以下の主要な属性があります。
  • name: FeignClientの名前を指定します。Ribbonを使用している場合、この名前はサービスディスカバリに使用されるマイクロサービスの名前となります。
  • configuration: Feignのカスタム設定クラスを指定します。Encoder、Decoder、ログレベル、Contractなどをカスタマイズできます。
  • contextId: FeignClientインスタンスを区別するために使用されます。
例えば、HBS_SERVICEというサービスがあり、そのサービスに含まれる多くの外部インターフェースを、一つのクラスにすべて定義したくないとします。その場合、以下のように複数のクライアントを定義するかもしれません。
public interface CustomerClient1 {
    // ... インターフェースメソッド
}

@FeignClient(name = "hbs-service", fallbackFactory = CustomerClientFallbackFactory.class)
public interface CustomerClient1 {
    // ... インターフェースメソッド
}
public interface CustomerClient2 {
    // ... インターフェースメソッド
}

@FeignClient(name = "hbs-service", fallbackFactory = CustomerClientFallbackFactory.class)
public interface CustomerClient2 {
    // ... インターフェースメソッド
}
これらのクライアントを定義すると、同じname属性を持つため、アプリケーション起動時に以下のエラーが発生します。
Description:
The bean 'optimization-user.FeignClientSpecification', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
このエラーを解決するには、2つの方法があります。 1. nameの重複を避け、contextIdに値を設定する。 2. nameが重複する場合でもBeanDefinitionの上書きを許可するために、spring.main.allow-bean-definition-overriding=trueを設定する。 2番目の方法を使用し、かつ特定のクライアントにカスタム設定を適用したい場合、設定が反映されない問題が発生することがあります。
@FeignClient(name = "hbs-service", configuration = FeignSupportConfig.class, fallbackFactory = CustomerClientFallbackFactory.class)
public interface CustomerClient2 {
    // ... インターフェースメソッド
}
BeanDefinitionの上書きが許可されている場合、対応するFeignContextの設定も上書きされてしまうため、この問題が発生します。この問題を解決するには、contextIdを個別に設定して、設定が正しく適用されるようにする必要があります。
@FeignClient(contextId = "CustomerClient2", name = "hbs-service", configuration = FeignSupportConfig1.class, fallbackFactory = CustomerClientFallbackFactory.class)
public interface CustomerClient2 {
    // ... インターフェースメソッド
}
この動作の背景には、FeignClientsRegistrarがFeignClientの名前に基づいて設定Beanを生成するロジックがあります。名前が同じ場合、後から定義されたものが前のものを上書きしてしまいます。
// FeignClientsRegistrar.java
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object clientName, Object configuration) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);
    builder.addConstructorArgValue(clientName);
    builder.addConstructorArgValue(configuration);
    registry.registerBeanDefinition(
            clientName + "." + FeignClientSpecification.class.getSimpleName(),
            builder.getBeanDefinition());
}
その後、FeignClientが呼び出されると、FeignClientFactoryBeanのgetTargetメソッドがFeignContextを取得してFeign.Builderを構築します。
// FeignClientFactoryBean.java
public <T> T getTarget() {
    FeignContext context = (this.beanFactory != null) 
        ? this.beanFactory.getBean(FeignContext.class) 
        : this.applicationContext.getBean(FeignContext.class);
    Feign.Builder builder = feign(context);
    // ... その他の設定とビルダーの構築
    return (T) loadBalancedTarget(builder, context, this.target);
}
FeignClientSpecificationが上書きされた結果、指定したカスタム設定が適用されなくなるのです。

タグ: Spring Cloud Feign Java

6月3日 18:35 投稿