The bean ‘xxx.FeignClientSpecification‘, defined in null, could not be registered.

本文探讨 FeignClient 在微服务项目中因重复注入相同实例名称而导致的启动失败问题。分析了问题产生的根源,并提供了三种解决方案:指定不同 contextId、调整 @EnableFeignClients 的配置或直接启用 Bean 定义覆盖。

问题描述:

微服务项目集成FeignClient有时会遇到如下启动失败提示:

***************************
APPLICATION FAILED TO START
***************************

Description:

The bean 'xxx.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

启动日志已经给出了解决该问题的方式,在配置文件中加入该配置即可:

setting spring.main.allow-bean-definition-overriding=true

原因分析

该问题其实并不是我第一次遇到,但是之前我并未深入探究造成该问题的原因,直接在配置文件中加入启动日志中提示的配置即可。借此机会咱们一探究竟。

FeignClientsRegistrar

//@EnableFeignClients->@Import({FeignClientsRegistrar.class})
//	-->registerBeanDefinitions()-->registerFeignClients()-->registerFeignClient()
private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
        String className = annotationMetadata.getClassName();
        BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
        this.validate(attributes);
        definition.addPropertyValue("url", this.getUrl(attributes));
        definition.addPropertyValue("path", this.getPath(attributes));
        String name = this.getName(attributes);
        definition.addPropertyValue("name", name);
        String contextId = this.getContextId(attributes);
        definition.addPropertyValue("contextId", contextId);
        definition.addPropertyValue("type", className);
        definition.addPropertyValue("decode404", attributes.get("decode404"));
        definition.addPropertyValue("fallback", attributes.get("fallback"));
        definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
        definition.setAutowireMode(2);
        //注入容器的bean名称
        String alias = contextId + "FeignClient";
        AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
        boolean primary = (Boolean)attributes.get("primary");
        beanDefinition.setPrimary(primary);
        String qualifier = this.getQualifier(attributes);
        if (StringUtils.hasText(qualifier)) {
            alias = qualifier;
        }
		//可以看到FeignClient注册到容器中的Bean定义的名称为alias
        BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, new String[]{alias});
        BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
    }
    
    private String getContextId(Map<String, Object> attributes) {
    	//优先拿contextId,其次获取name
        String contextId = (String)attributes.get("contextId");
        if (!StringUtils.hasText(contextId)) {
            return this.getName(attributes);
        } else {
            contextId = this.resolve(contextId);
            return getName(contextId);
        }
    }

    String getName(Map<String, Object> attributes) {
    	//优先serviceId,其次获取name,再其次获取value
        String name = (String)attributes.get("serviceId");
        if (!StringUtils.hasText(name)) {
            name = (String)attributes.get("name");
        }

        if (!StringUtils.hasText(name)) {
            name = (String)attributes.get("value");
        }
        name = this.resolve(name);
        return getName(name);
    }

源码解析

结合上述关键源码,我们可以发现FeignClient是容器通过扫描解析注解,将我们的FeignClient注入容器的,造成我们启动失败的原因是重复向容器中注入相同实例名称,由此可见,若我们在定义FeignClient的时候将同一服务分别放在多个FeignClient中,就会导致此问题发生。因此我们可以通过指定不同的contextId来避免命名冲突。

但是,我十分肯定我的工程中并不存在这种情况!为什么还会报这种错误呢?带着疑问我在FeignClientsRegistrar类中的registerBeanDefinitions方法打了断点观察,我发现这个方法竟然执行了两次?!此时我就猜想是不是@EnableFeignClients配置的basePackage重复导致目录被重复扫描,果然发现我们自定义插件中已经指定了com.xxx路径下默认扫描,工程的启动类中又再次的启用了@EnableFeignClients注解,导致同一个FeignClient被扫描了两次。

解决方案

1.若相同服务的确需要划分多个FeignClient,那么需要指定不同的contextId
@FeignClient(name=“a-service”,contextId=“aServiceContext1”)
@FeignClient(name=“a-service”,contextId=“aServiceContext2”)

2.注意@EnableFeignClients的启动类包路径是否重复

3.若确保Bean覆盖不影响可直接开启覆盖

setting spring.main.allow-bean-definition-overriding=true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值