场景
我在一个项目开发中需要远程调用其他微服务的接口进行业务处理,由于涉及到多个接口的调用,所以我根据业务分了两个FeignClient接口,但是使用的服务名是同一个,代码如下
用户相关的服务
@FeignClient(value = "base-data")
public interface UserClient{
// 省略无关代码....
}
推送相关的服务
@FeignClient(value = "base-data")
public interface MessageClient{
// 省略无关代码....
}
分好之后就需要进行开发调试,在启动项目的过程中出现异常导致项目无法正常启动,异常信息如下
Description:
The bean 'base-data.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
Disconnected from the target VM, address: '127.0.0.1:53658', transport: 'socket'
Process finished with exit code 1
异常信息提示是由于名称为 base-data.FeignClientSpecification 在Spring环境中已经注册了,无法再次进行注册
在此异常信息上面还有一条异常信息
org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'base-data.FeignClientSpecification' defined in null: Cannot register bean definition
根据抛出的异常可以判断是处理BeanDefinition是发生的异常
解决
直接根据异常名称 BeanDefinitionOverrideException 进行搜索定位具体的异常类,使用idea查看该类在什么地方被使用到,于是发现只有一处代码使用了该类,就是在DefaultListableBeanFactory#registerBeanDefinition()
通过源码可以看出来是在进行BeanDefinition注册的时候发现该名称的BeanDefinition已经存在,然后又去判断是否允许BeanDefinition被覆盖,Spring在启动时默认使用false,也就是不允许覆盖,于是就抛出异常
但是上面两个FeignClient除了服务名相同其他都不相同,于是通过调试来查看最终注册的beanName是什么
在异常地方打个断点通过控制台查看方法调用栈
通过调用栈可以看到该方法是在FeignClientRegistrar#registerBeanDefinitions方法中调用的,先从registerBeanDefinitions中一步步调试,发现有两处地方调用了DefaultListableBeanFactory的registerBeanDefinition方法,
在经过调试发现异常是在第一处注册BeanDefinition抛出的,于是进入registerClientConfiguration方法中查看
可以看到在调用registry.registerBeanDefinition()时使用的名称是当前方法传入的,在上一张图中可以看到name是通过getClientName()获取的,于是进入该方法中
该方法的入参client其实就是@FeignClient注解中的参数,方法先判断contextId是否为空,如果不为空就用contextId作为name,否则就使用value作为bean名称, value值也就是base-data,所以两个类使用相同的服务名但是不指定contextId,就会出现BeanDefinitionOverrideException 异常
改成下面这种就可以解决问题
@FeignClient(value = "base-data", contextId = "userClient")
public interface UserClient{
// 省略无关代码....
}
@FeignClient(value = "base-data"。, contextId = "messageClient")
public interface MessageClient{
// 省略无关代码....
}