在本文中,我将讨论棘手的Spring Boot bean定义覆盖机制。

为了使您对该主题更加清楚,让我们从小测验开始。请看下一个简单的例子。

因此,我们有2种配置,它们使用名称beanName实例化bean,在主应用程序中,我们仅打印该bean的值(非常重要的是,它们都具有相同的名称)。

那么您认为将要打印什么?

示例1

@SpringBootApplication
public class Application {public static void main(String[] args) {ApplicationContext applicationContext = SpringApplication.run(Application.class, args);System.out.println(applicationContext.getBean("beanName"));}
}@Configuration
class config1 {@Primary@Order(Ordered.HIGHEST_PRECEDENCE)@BeanString beanName() {return "BEAN1";}
}@Configuration
class config2 {@BeanString beanName() {return "BEAN2";}
}

可能的答案:

  1. BEAN1 ”将被打印。可能是因为它具有@Primary注释,甚至还有@Order
  2. BEAN2 ”将被打印。
  3. 异常会被抛出,因为它不允许有几个豆同名。
  4. 还有其他版本吗?

正确答案

奇怪的是,正确答案对于spring boot 1.*spring boot 2.*版本会有所不同。

如果您使用spring boot 1- 运行此代码,**“ BEAN2”**将被打印在控制台中。用spring boot 2- exception将被抛出。你知道正确的答案吗?如果是,则可能是您在Pivotal工作:)

让我们一个一个地走:对于spring boot 1。如果我们查看日志,则会在此找到下一行:

INFO --- [main] o.s.b.f.s.DefaultListableBeanFactory:
Overriding bean definition for bean 'beanName' with a different definition:
replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=true; factoryBeanName=config1; factoryMethodName=beanName; initMethodName=null; destroyMethodName=(inferred);
defined in class path resource [com/example/test/config1.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=config2; factoryMethodName=beanName; initMethodName=null; destroyMethodName=(inferred);
defined in class path resource [com/example/test/config2.class]]

因此,config1bean被覆盖,config2 并打印了**“ BEAN2”**。

对于spring boot 2。如果我们查看日志,则会在此找到下一行:

***************************
APPLICATION FAILED TO START
***************************
Description:
The bean 'beanName', defined in class path resource [com/example/test/config2.class],
could not be registered. A bean with that name has
already been defined in class path resource [com/example/test/config1.class]
and overriding is disabled.Action:
Consider renaming one of the beans or enabling overriding
by setting spring.main.allow-bean-definition-overriding=true

因此,在spring boot 2默认情况下,行为已更改,并且Bean覆盖已不再是有效情况。如果要修复此问题并使其与之相似,spring boot 1则应添加下一个配置: spring.main.allow-bean-definition-overriding=true

从现在开始,他们以相同的方式工作。

但这还不是终点。让我们检查示例2:

示例2

@SpringBootApplication
public class Application {public static void main(String[] args) {ApplicationContext applicationContext = SpringApplication.run(Application.class, args);System.out.println(applicationContext.getBean("beanName"));}
}@Configuration
class config1 {@BeanString beanName() {return "BEAN1";}
}@Configuration
class a_config2 {@BeanString beanName() {return "BEAN2";}
}

因此完全相同,但是第二个配置类的名称有所不同:现在是a_config2,但也可以这么说config0

现在,如果我们运行此代码,结果将为BEAN1

那怎么可能呢?答案。

  1. Spring完全忽略了具有相同名称(如@Primary和)的bean的任何其他注释@Order。在这种情况下,他们不会进行任何更改。
  2. Spring以无法预测的方式处理@Configurations。在示例2中,它按NAME的顺序对配置类进行排序,因此基于该类可以覆盖另一个,这在示例1示例2中可以看到。
  3. 在更复杂的应用程序中,可能有其他配置xml loaded with @Import(Configuration.class)/groovy/whatever。在这种情况下,行为将再次有所不同。我不知道哪一个将被最新加载并覆盖前一个。而且我在Spring文档中没有找到任何对此的有力解释。

我发现,@Import总是总是首先加载,而XML配置总是最新,因此它将覆盖其他所有内容。在这种情况下,名称无关紧要。

请检查最新示例:

@SpringBootApplication
@ImportResource("classpath:config.xml")
@Import(Config0.class)
public class Application {public static void main(String[] args) {ApplicationContext applicationContext = SpringApplication.run(Application.class, args);System.out.println(applicationContext.getBean("beanName"));}
}@Configuration
class config1 {@BeanString beanName() {return "BEAN1";}
}@Configuration
class config2 {@BeanString beanName() {return "BEAN2";}
}//separate java config which is loaded by @Import
@Configuration
class Config0 {@BeanString beanName() {return "BEAN0";}
}//separate xml config which is loaded by @ImportResource
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation = "http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd"><bean id = "beanName"  class = "java.lang.String"><constructor-arg value="XML_BEAN"></constructor-arg></bean></beans>

因此,这里的输出将是:“ XML_BEAN”

因此,几乎不可能预测哪个bean会覆盖另一个bean,尤其是当您具有复杂的上下文且内部有许多不同的配置并且确实令人困惑时。

摘要

从此示例中可以看到,这种行为是完全不可预测的,在这里犯错误是非常容易的。我在这里只能看到一条规则:

与另一个名称相同(稍后处理)的Bean会覆盖较旧的Bean,但尚不清楚以后将处理哪个。

导致我们如此困惑的机制称为bean覆盖。当Spring遇到一个声明与上下文中已经存在的另一个bean同名的bean时,使用它。

我面对这个问题的真实例子。我们有一个针对Spring RestTemplate的自定义配置。名称只是restTemplate。在一段时间之后,我们从外部依赖项的配置中获得了另外一个名称完全相同的restTemplate。当然发生了,外部restTemplate用我们的自定义“调整”覆盖了我们自己的模板

经过调查,我发现春季如何处理此类情况。

解决方案

  1. 首先,我强烈建议您启用此配置: spring.main.allow-bean-definition-overriding=false 它会立即为您提供一个信息,说明您具有相同名称的bean,并且它们之间存在冲突。
  2. 如果此代码是您的代码,并且可以以任何方式更改Bean的名称-只需执行此操作并注入所需的代码即可。而且您将永远不会面对这个问题。
  3. 如果出于某些原因,第2点对您而言不是一种情况-我建议您尝试排除错误的bean。如您所见,很难预测哪个bean将被覆盖,因此从上下文中删除它会更好。

这是一个例子:

@SpringBootApplication
@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = config2.class))
public class Application {public static void main(String[] args) {ApplicationContext applicationContext = SpringApplication.run(Application.class, args);System.out.println(applicationContext.getBean("beanName"));}
}@Configuration
class config1 {@BeanString beanName() {return "BEAN1";}
}@Configuration
class config2 {@BeanString beanName() {return "BEAN2";}
}

因此,在这种情况下,不会扫描config2.class,因此我们只有一个beanName,结果将是**“ BEAN1”**。

PS:如果您发现一些空白或有任何需要补充或讨论的地方-请随时发表评论。

Spring Boot: Bean definition overriding相关推荐

  1. Spring Boot Bean的使用,@Repository,@Service,@Controller,@Component

    前言 在Spring MVC的时候,我们使用xml来配置bean,如今的Spring boot推荐我们使用元注解的发生,那就听Spring Boot的推荐,下面我就为大家来介绍下Spring Boot ...

  2. spring boot实战(第十篇)Spring boot Bean加载源码分析

    前言 前面的文章描述了Application对应Bean的创建,本篇将阐述spring boot中bean的创建过程 refresh 首先来看SpringApplication#run方法中refre ...

  3. Spring Boot文档阅读笔记-Spring Boot @Bean解析

    利用SpringBoot的@Bean创建一个简单的Bean. Spring的@Bean注解是放在方法上的,带上这个注解的方法会被Spring容器管理.并且这个方法要返回一个值(对象),这个值和对象会被 ...

  4. Spring Boot@Bean

    Spring的核心容器 Spring全家桶从原来的SpringMVC到现在的SpringBoot.核心容器包括了Beans.Core.Context.SpEL. core和beans模块提供了整个框架 ...

  5. spring boot: Bean的初始化和销毁 (一般注入说明(三) AnnotationConfigApplicationContext容器 JSR250注解)...

    import org.springframework.context.annotation.AnnotationConfigApplicationContext; 使用AnnotationConfig ...

  6. Spring Boot : Bean标签属性

    1.美图 2.概述 属性 解释 ① id属性 Bean的名称在IOC容器中必须是唯一的. ② class属性 指定Bean对应实现类的全类名,即包名加类名,必须有无参构造. ③ scope属性: 前面 ...

  7. spring boot application.properties 属性详解

    2019年3月21日17:09:59 英文原版: https://docs.spring.io/spring-boot/docs/current/reference/html/common-appli ...

  8. Spring Boot 2.0 常用配置描述(官网翻译)

    常用配置描述 我们可以在application.properties文件中,application.yml文件中或命令行开关中指定各种属性. 本附录提供了常用Spring Boot属性的列表以及对使用 ...

  9. [201903][Spring Boot 编程思想][核心篇][小马哥][著]

    [201903][Spring Boot 编程思想][核心篇][小马哥][著] The Java Community Process(SM) Program https://jcp.org/en/ho ...

最新文章

  1. C Primer Plus (第五版) 第十章 数组和指针 编程练习
  2. 字符串格式化---StrFormatter
  3. 杭电ACM刷题(1):1002,A + B Problem II
  4. [转]const使用详解
  5. activiti7流程设计器_基于容器和微服务应用的架构:容器设计原则
  6. IDEA中import自己的python包方法
  7. Oracle Bitmap 索引结构、如何存储及其优势
  8. 简易VR眼镜:是玩具还是工具?
  9. aida32系统信息报告
  10. html 超出shengl,逃不掉(GL)作者:乔禾若
  11. RTC月度小报5月 |教育aPaaS灵动课堂升级、抢先体验VUE版 Agora Web SDK、声网Agora与HTC达成合作
  12. Android手机ERP开发(二)
  13. vue3语法糖父子组件的通信
  14. 机器学习算法-线性回归
  15. 五、Springboot 整合Shiro---03认证---第三方QQ登陆
  16. 我的世界java版_我的世界Java版1.16.5
  17. 名企笔试真题精选 (四)
  18. java打字游戏和解析_java类与对象案例之打字游戏
  19. Sqlite 获取一小时内数据sql
  20. 项目进度管理 试题分析

热门文章

  1. 微信公众号文章增加评论功能
  2. Android通讯录模糊匹配搜索实现(号码、首字母,移动应用开发课程设计心得
  3. 大快人心,盗版CH430芯片的被判刑了,公司被罚400万
  4. linux升级 nginx报错,Linux下升级nginx,编译安装nginx-sticky-module
  5. 如何使用音频剪辑软件,快速剪辑任意格式音频!
  6. windows2000光盘完全解读
  7. 深圳湾口岸没有直达香港机场
  8. AM335x(TQ335x)学习笔记——Nandamp;amp;网卡驱动移植
  9. Data-Mining试题
  10. 鸟哥的Linux私房菜——第十章