1.エラーの説明
最近、SpringBoot 2.2.11.RELEASEを使用してFeign開発と連携すると、次のエラーが発生しました。
org.springframework.beans.factory.support.BeanDefinitionOverrideException:
Invalid bean definition with name 'XXX.FeignClientSpecification' defined in null:
Cannot register bean definition [Generic bean: class [org.springframework.cloud.openfeign.FeignClientSpecification]; bound.
Description:
The bean 'XXX.FeignClientSpecification' could not be registered.
A bean with that name has already been defined and overriding is disabled.
2.問題の原因
エラーの説明によると、Bean'XXX.FeignClientSpecification 'を登録できませんでした。その名前のBeanはすでに定義されており、オーバーライドは無効になっています。以下に示すように、プロジェクトで2つのFeignクライアントが定義されています。
@FeignClient(value = "XXXX")
public interface ApiService1 {
@GetMapping("/user/{username}")
UserDto query(@PathVariable("username") String username);
}
@FeignClient(value = "XXX")
public interface MenuApiService {
@GetMapping("/allMenus")
List<MenuDto> queryAll();
}
2つのFeignクライアントで構成された値が同じであるため、起動時に上記のエラーが報告されます。
三、解決策
アノテーション@FeignClientにcontextId 属性を追加し、 異なる値を割り当てます。それは解決することができます。
4、分析
スタートアップクラスに追加された@EnableFeignClientsアノテーションを見てみましょう。 これにより、以下に示すようにFeignClientsRegistrarクラスが導入 されます。
次に、FeignClientsRegistrarクラスのソースコードを分析し ています
1.継承階層
このクラスは、Beanを動的に登録するために使用されるImportBeanDefinitionRegistrarインターフェースを実装していることが わかります。インターフェイス情報は次のとおりです。
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
2. FeignClientsRegistrarで実装クラス
FeignClientsRegistrarのクラスが実装 registerBeanDefinitionsの方法ImportBeanDefinitionRegistrarのインターフェースを 、次のように:
次に、registerFeignClientsメソッドの実装に焦点を当て ましょう。このメソッドのロジックは非常に単純で、主に @EnableFeignClientsアノテーションで設定されたclients属性とbasePackages属性です。プロジェクトで@FeignClient アノテーションで構成されたすべてのクラスまたはインターフェイスをスキャンします。。メソッド分析は、具体的にはFeignClientクライアントにすることができます。このメソッドのメインコードを次のように見てください(他のソースコードを確認できます)。
メインコードは次のとおりです。
1. getClientName(attributes)メソッドを呼び出して、メイン名を取得します。ソースコードは次のとおりです。
private String getClientName(Map<String, Object> client) {
if (client == null) {
return null;
}
String value = (String) client.get("contextId");
if (!StringUtils.hasText(value)) {
value = (String) client.get("value");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("name");
}
if (!StringUtils.hasText(value)) {
value = (String) client.get("serviceId");
}
if (StringUtils.hasText(value)) {
return value;
}
throw new IllegalStateException("Either 'name' or 'value' must be provided in @"
+ FeignClient.class.getSimpleName());
}
このメソッドのコードロジックは非常に単純です。contenxtIdを取得します。存在しない場合は値を取得し、値が存在しない場合は名前を取得して順番に取得します。
2.コール registerClientConfigurationを次のように豆、ソースコードを登録する方法です。
private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,
Object configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientSpecification.class);
builder.addConstructorArgValue(name);
builder.addConstructorArgValue(configuration);
// 关键点:根据 getClientName 方法获取的值 注册 Bean 。
registry.registerBeanDefinition(
name + "." + FeignClientSpecification.class.getSimpleName(),
builder.getBeanDefinition());
}
ではgetClientName()
、ソースコード、名前がcontenxtId、名前、値、(期限切れマーク)@FeignClient注釈でいるServiceIDであることがわかります。beanName:name + "." + FeignClientSpecification.class.getSimpleName()
したがって、同じ名前のFeigClientアノテーションを使用すると、同じBean名がIocに挿入されます。したがって、エラーはFeignClientSpecificationクラスが原因で発生します。