最近提升项目的SpringCloud版本后出错误导致项目无法启动

关键词

The bean 'xxx.FeignClientSpecification' could not be registered. A bean with that name has already been defined and overriding is disabled.

版本信息

升级前版本

SpringBoot SpringCloud
2.0.6.RELEASE Finchley.SR2

升级后版本

SpringBoot SpringCloud
2.2.5.RELEASE Hoxton.SR3

错误内容

2020-03-27 14:35:02.481  [ main:10483 ] - [ERROR ,,, ] o.s.boot.diagnostics.LoggingFailureAnalysisReporter#report:40 -

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

Description:

The bean 'xx-xxx.FeignClientSpecification' could not be registered. A bean with that name has already been defined and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

错误原因:

说的很明显BeanName重复了, 容器内出现了两个xx-xxx.FeignClientSpecification的实现类

分析:

首先,我们项目内没有名称带有FeignClientSpecification的类或接口,所以我认为这应该是Fegin框架自己命名的,

名称特点是"服务名-FeignClientSpecification"

网上解决方案:

方案一:

同一个服务的接口合并到同类中去, 就是将同一个service的接口整合到同一个FeignClient中, 方便统一

方案二:

开启Bean覆盖, 说法是2.0.x版本默认就是true, 新版2.2.x变为了false

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

个人分析:

方案一, 根本就不方便, 严重破坏了单一原则的设计模式, 一个服务几十甚至上百个不同业务的接口堆在一个类上就方便维护了?

方案二: 确实简单粗暴, 反正我们注入是通过接口类型注入的, 名称重复问题不大顶多就是有些警告而已, 但是我认为新版本官方默认不允许BeanName重复肯定是有道理的, 所以我也不打算开启.

所以我通过源码分析找到了方案三: 设置@FeignClient中的contextId字段

源码分析:

既然说是BeanName名称重复, 那就必须先找到这个名称怎么来的, 通过关键字"could not be registered."搜索框架源码找到提示位置

org.springframework.boot.diagnostics.analyzer.BeanDefinitionOverrideFailureAnalyzer#getDescription

 private String getDescription(BeanDefinitionOverrideException ex) {StringWriter description = new StringWriter();PrintWriter printer = new PrintWriter(description);printer.printf("The bean '%s'", ex.getBeanName());if (ex.getBeanDefinition().getResourceDescription() != null) {printer.printf(", defined in %s,", ex.getBeanDefinition().getResourceDescription());}printer.printf(" could not be registered. A bean with that name has already been defined ");if (ex.getExistingDefinition().getResourceDescription() != null) {printer.printf("in %s ", ex.getExistingDefinition().getResourceDescription());}printer.printf("and overriding is disabled.");return description.toString();}

看到提示重复name的值来源是ex.getBeanName()

在此处下一个条件断点"xx-xxx.FeignClientSpecification".equals(ex.getBeanName())得到调用栈

可以看到调用栈并不深, 通过调用栈向上寻找触发来源

发现这个错误是在SpringBoot的run方法执行过程中抛出来的的错误, 我们知道这个方法主要就是SpringBoot的生命周期执行方法

任何一个环节都有可能抛出异常, 由于我不知道这个Bean注册的时机是在生命周期的哪一段就知道能一段段试了吗?

我换了一个思路, 我找这个异常类的构造方法并在里边下断点不就知道这个异常是谁new的了吗

org.springframework.beans.factory.support.BeanDefinitionOverrideException

重新运行

果不其然发现了其实例化的调用栈, 向上找就发现了BeanName重复判断,配置值开关是否打开判断的相关代码, 但是不够, 继续向上找, 发现了名称的来源

org.springframework.cloud.openfeign.FeignClientsRegistrar#registerClientConfiguration

 private void registerClientConfiguration(BeanDefinitionRegistry registry, Object name,Object configuration) {BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(FeignClientSpecification.class);builder.addConstructorArgValue(name);builder.addConstructorArgValue(configuration);registry.registerBeanDefinition(name + "." + FeignClientSpecification.class.getSimpleName(),builder.getBeanDefinition());}

看到名称来源是 name+"."+FeignClientSpecification的简单名称, 可以看到后缀是写死的没有修改的可能, 我就从name能不能修改方面下手, 继续向上寻找name的来源

发现了beanName是通过一个getClientName()方法来的, 进入getClientName()方法

org.springframework.cloud.openfeign.FeignClientsRegistrar#getClientName

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

通过内存变量查看client变量的内容, 我通过值内容初步判断断里边的值就是我们@FeignClient注解里边的键和值

那我就知道了这段代码是优先取contextId的值作为name的, 如果取不到才取别的, 都取不到就报错.

顺序是contextId->value->name->serviceId

那我通过猜测去到@FeignClient注解看看是否有contextId字段

事实也证实了我的猜测, 确实有这个字段而且注释也说的很清楚

意思: 这将用作Bean名称,而不是名称(如果存在),但不会用作服务ID。

解决方法:

@FeignClient中填写contextId字段信息即可(PS:我们项目很多Fegin接口,我改了很久...但我没想到别的方法)

@FeignClient(value = "xx-xxx", qualifier = "itemTemplateApiclient", contextId = "itemTemplateApiclient")
public interface TtemTemplateApiClient extends ItemTemplateApi {

@FeignClient中qualifier参数是我们之前SpringBoot2.0.x版本加的,  因为之前SpringIOC容器里边用的是qualifier参数作为某个name, 不指定的话没关系,但是启动的时候会有提示, 我喜欢干净的控制台信息我才加的, 对于这次的问题是不影响的.

疑问,官方为什么不直接取我们接口的类名作为name呢? 可能是担心不同包的同名类,也可能是fallback也是继承Fegin的时候就会出现同接口的两个类

算了吧,问题解决了就好...

升级SpringCloud到Hoxton.SR3出现The bean 'xxx.FeignClientSpecification' could not be registered. 源码分析和解决相关推荐

  1. 升级SpringCloud到Hoxton.SR3后使用Fegin出现jackson反序列化失败,源码分析,原因lombok版本升级

    关键词 Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct insta ...

  2. 框架源码专题:springIOC的加载过程,bean的生命周期,结合spring源码分析

    文章目录 1.BeanFactory和ApplicationContext的区别? 2. IOC与 Bean的加载过程 ①:初始化容器DefaultListableBeanFactory ②:创建读取 ...

  3. The bean ‘xxx.FeignClientSpecification‘ could not be registered

    spring cloud 项目启动报错: 原因:项目中存在多个接口使用@FeignClient调用同一个服务 解决办法: 1.按照提示,Consider renaming one of the bea ...

  4. Spring IOC 容器源码分析 - 获取单例 bean

    1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ...

  5. Spring IOC 容器源码分析 - 填充属性到 bean 原始对象

    1. 简介 本篇文章,我们来一起了解一下 Spring 是如何将配置文件中的属性值填充到 bean 对象中的.我在前面几篇文章中介绍过 Spring 创建 bean 的流程,即 Spring 先通过反 ...

  6. Spring IOC 容器源码分析 - 创建原始 bean 对象

    1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续.在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程.本篇文章,我们就从战术的层面上,详细分析doCreat ...

  7. Spring IOC 容器源码分析 - 创建单例 bean 的过程

    1. 简介 在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)的实现逻辑.对于已实例化好的单例 bean,getBean(String) 方法并不会再一次去 ...

  8. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  9. Spring Ioc源码分析 之 Bean的加载(6):属性填充(populateBean())

    "属性填充",也是在populateBean()方法中. 首先回顾下CreateBean的主流程: 如果是单例模式,从factoryBeanInstanceCache 缓存中获取B ...

最新文章

  1. jQuery动画的显示与隐藏效果
  2. Leetcode Excel Sheet Column Number
  3. SQL语句中between and 范围
  4. vsnprintf的作用和使用
  5. MoeCTF 2021Re部分------Algorithm_revenge
  6. MFC中动态创建控件以及事件响应实现方法
  7. 《长安十二时辰》背后的文娱大脑:如何提升爆款的确定性?
  8. .NET Core IdentityServer4实战 第二章-OpenID Connect添加用户认证
  9. opencv(二) 图片处理
  10. 纪元java游戏_RPG纪元
  11. Vim 重复操作的宏录制
  12. 《Python编程从入门到实践》记录之类存储在模块及其导入
  13. 《数据结构与抽象:Java语言描述(原书第4版)》一1.1 什么是包
  14. Kibana 6.2.3修改本地时区
  15. html获取当前ip地址_科普文:Node.js 安全攻防 - 如何伪造和获取用户真实 IP ?
  16. Linux下安装python27
  17. 晶体晶振小结(XTAL与TCXO/VCXO/VC-OCXO等)
  18. 绩效考核方法:绩效目标怎么确定_做到商学院
  19. OJ 2311 Problem A Orange
  20. 试验设计——拉丁超立方抽样

热门文章

  1. Apache之AllowOverride参数详解
  2. python 提取txt某一段内容_(转)提取TXT文本中指定内容——python
  3. 颗粒无收与盆满钵满,李子柒缘何与微念翻脸?
  4. day06. 一、字典Dict
  5. 关于Unity中特殊符号的输入以及格式的调整
  6. 从数据恢复角度分析腾讯云静默损坏
  7. Mac上的BG Renderer MAX(ae多线程渲染工具)
  8. 梦想世界2014年5月29日服务器维护公告,《梦想世界》2018年3月29日维护公告
  9. 手机上计算机记忆功能怎么清,怎么清除搜狗输入法自动记忆功能?
  10. Nginx运行FastCGI程序(ngx_http_fastcgi_module模块、fcgi库、spwan-fcgi进程管理器)