天天看點

Feign報錯‘xx.FeignClientSpecification‘ could not be registered. overriding is disabled

一、錯誤描述

    最近使用 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.
           

二、問題原因

根據錯誤描述是 The bean 'XXX.FeignClientSpecification' could not be registered. A bean with that name has already been defined and overriding is disabled. 在項目中定義了兩個 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();

}
           

因為 兩個 Feign 用戶端中配置的 value 值是一樣的 導緻在啟動時報如上錯誤。

三、解決方法

在 注解 @FeignClient 中添加  contextId  屬性,并且賦予不同的值。即可解決。

四、分析

我們看在 啟動類上添加的  @EnableFeignClients 注解 其中引入了  FeignClientsRegistrar 類如下所示:

Feign報錯‘xx.FeignClientSpecification‘ could not be registered. overriding is disabled

那麼我們接下來在分析一下 FeignClientsRegistrar 類的源碼

 1、繼承層次

Feign報錯‘xx.FeignClientSpecification‘ could not be registered. overriding is disabled

我們看到此類實作了 ImportBeanDefinitionRegistrar 接口,此接口的作用就是動态注冊 Bean。接口資訊如下所示:

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 類實作了 ImportBeanDefinitionRegistrar 接口的  registerBeanDefinitions 方法、如下所示:

Feign報錯‘xx.FeignClientSpecification‘ could not be registered. overriding is disabled

接下來重點看下 registerFeignClients 方法的實作,此方法 邏輯很簡單,主要就是 @EnableFeignClients 注解中設定的 clients 屬性和  basePackages 屬性掃描項目中所有配置了 @FeignClient  注解的類或接口,方法解析具體可 FeignClient 用戶端。看下這個方法中的主要代碼(其他的可自行檢視源碼)如下所示:

Feign報錯‘xx.FeignClientSpecification‘ could not be registered. overriding is disabled

主要代碼就是:

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,如果不存在在擷取 value, value 不存在 擷取name 依次擷取。

 2、調用  registerClientConfiguration 方法注冊 Bean, 源碼如下所示:

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()

源碼中,可以看到 name 就是 @FeignClient 注解中的 contenxtId,name,value, serviceId (已标注過期)。beanName:

name + "." + FeignClientSpecification.class.getSimpleName()

故而我們在使用相同名稱的FeigClient注解時,注入到Ioc的是相同Bean名。是以錯誤是由FeignClientSpecification類引起的。

繼續閱讀