spring.main.allow-bean-definition-overriding=true 是一种不好的做法吗?

5
我正在构建一个Spring Boot Rest API服务器,将旧应用移植到更强大的框架上。这是我第一次使用这些技术。到目前为止,我已经建立了一个概念验证,其中包含两个API,以JSON方式回复“Hello world!”。其中一个是开放的,另一个是OAuth2安全保护的。我已经完成了根据要求调整安全性的工作。
我还使用Rest-Assured进行集成测试和Spring MockMvc进行单元测试,建立了一个强大的测试结构,因此我有信心开始实施真正的API,采用TDD方法。
我们使用Maven。我的问题是,一旦我依赖于现有的组件,就会出现以下堆栈跟踪。我知道它的意思,但我们的代码库很大,我找不到原因所在。当我在Google上查询这个问题时,我通常会找到简单建议在属性中添加spring.main.allow-bean-definition-overriding=true。这不是掩耳盗铃吗?我猜测Spring默认不允许这样做有很好的理由。忽略该错误并使用方便的应用程序属性会有哪些可能的后果?
堆栈跟踪:
*************************** APPLICATION FAILED TO START
***************************

Description:

The bean 'org.springframework.transaction.config.internalTransactionAdvisor', defined in class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class], 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

[WARNING]  java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0 (Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke (Method.java:498)
    at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run (AbstractRunMojo.java:558)
    at java.lang.Thread.run (Thread.java:748) Caused by: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'org.springframework.transaction.config.internalTransactionAdvisor' defined in class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration; factoryMethodName=transactionAdvisor; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]] for bean 'org.springframework.transaction.config.internalTransactionAdvisor': There is already [Root bean: class [org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] bound.
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition (DefaultListableBeanFactory.java:897)
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod (ConfigurationClassBeanDefinitionReader.java:274)
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass (ConfigurationClassBeanDefinitionReader.java:141)
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions (ConfigurationClassBeanDefinitionReader.java:117)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions (ConfigurationClassPostProcessor.java:327)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry (ConfigurationClassPostProcessor.java:232)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors (PostProcessorRegistrationDelegate.java:275)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors (PostProcessorRegistrationDelegate.java:95)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors (AbstractApplicationContext.java:705)
    at org.springframework.context.support.AbstractApplicationContext.refresh (AbstractApplicationContext.java:531)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh (ServletWebServerApplicationContext.java:142)
    at org.springframework.boot.SpringApplication.refresh (SpringApplication.java:775)
    at org.springframework.boot.SpringApplication.refreshContext (SpringApplication.java:397)
    at org.springframework.boot.SpringApplication.run (SpringApplication.java:316)
    at org.springframework.boot.SpringApplication.run (SpringApplication.java:1260)
    at org.springframework.boot.SpringApplication.run (SpringApplication.java:1248)
    at ca.mycompany.oav.ResourceServerApplication.main (ResourceServerApplication.java:22)
    at sun.reflect.NativeMethodAccessorImpl.invoke0 (Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke (Method.java:498)
    at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run (AbstractRunMojo.java:558)
    at java.lang.Thread.run (Thread.java:748)

3
有些Spring的默认设置似乎比其他设置更合理(看着你,OSIV),但这个似乎是明智的。这个问题似乎与此相关:https://dev59.com/2VQJ5IYBdhLWcg3wWkb9。 - Nathan Hughes
1
我从未使用过它。感觉你应该审查一下你的bean定义。 - duffymo
3个回答

5

请注意:通过添加这个,很难猜测哪个bean会有优先级,因为bean的创建顺序主要由运行时影响的依赖关系决定。 因此,除非我们足够了解bean的依赖层次结构,否则允许bean覆盖可能会产生意外行为。

参考


1

spring.main.allow-bean-definition-overriding=true 关注覆盖使用相同的 bean 名称作为现有 bean。如果该 bean 在您自己的代码中,则可以随时更改 bean 名称(如果使用 @Bean,则名称默认为方法名称,或者对于 @Component,名称为类名;但是有不同的设置 bean 名称的方法,请查看文档)。

因此,我所能想到的唯一合理的原因(就我所知)设置此标志的原因是加载具有相同名称的多个您无法控制的 bean(即来自第三方代码)。


请注意,如果您有多个类型相同的bean,并且在不使用名称/限定符的情况下自动装配此bean,则会导致初始化崩溃。 - David Barda

0

答案是

尽量不要使用覆盖。但有时可能需要(但要小心)

为了更好地理解这一点,

当您启动应用程序时,Spring Boot会自动加载许多bean(以提供开箱即用的功能),其中一些可能是必需的,而另一些则不是。我们有两个选择:

选项#1:

  1. 让Spring Boot加载功能
  2. 稍后再覆盖

选项#2:

  1. 要求Spring Boot不加载您不需要的内容。
  2. 只需添加自己的bean

示例用例

Spring Boot提供了带有ConfigServicePropertySourceLocator的开箱即用的Cloud Configuration。在实时场景中,我们将拥有自己的安全配置服务器来代替默认配置服务器。

选项#1:

  1. 让Spring Boot加载ConfigServicePropertySourceLocator Bean
  2. 通过指定spring.main.allow-bean-definition-overriding=true启用覆盖
  3. 定义您的自定义Bean,但请确保添加@Order、@Primary等注解。

选项#2:

  1. 通过添加spring.cloud.config.enabled=false,要求Spring Boot不加载ConfigServicePropertySourceLocator
  2. 只需定义你的自定义Bean。

这样可以节省启动时间并使代码更清洁。

参考: https://github.com/spring-cloud/spring-cloud-config/issues/177


这个答案有些不正确(或者至少是误导性的),因为 spring.main.allow-bean-definition-overriding=true 关注的是使用相同的 bean 名称覆盖现有的 bean。你的例子是关于使用相同的 bean 类型进行覆盖,这总是被允许的。 - jhyot
是的。可以完全按名称或优先级覆盖。Nut的想法是尽可能避免覆盖。如果我们有选项禁用默认行为并加载自定义组件。 - Sathish Kumar Thiyagarajan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接