一、错误描述
最近使用 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 类如下所示:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL9UVbkZHeyg1c4dVW050MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2IjN4QDNzkDM0IjMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
那么我们接下来在分析一下 FeignClientsRegistrar 类的源码
1、继承层次
我们看到此类实现了 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 方法、如下所示:
接下来重点看下 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,如果不存在在获取 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类引起的。