Spring Boot中的BeanDefinitionOverrideException

2023/05/11

1. 简介

Spring Boot 2.1升级让人们意外地发现了BeanDefinitionOverrideException,它可能会使开发人员感到困惑,并使他们想知道Spring中的bean覆盖行为发生了什么。

在本教程中,我们将解决这个问题,并了解如何最好地解决它。

2. Maven依赖

对于我们的示例Maven项目,我们需要添加[Spring Boot Starter](https://search.maven.org/classic/#search ga 1 g%3A”org.springframework.boot” AND a%3A”spring-boot-starter”)依赖项:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>

3. Bean覆盖

Spring bean在ApplicationContext中由它们的名称标识。

因此,当我们在ApplicationContext中定义一个与另一个bean同名的bean时,bean覆盖是一种默认行为。它的工作原理是在名称冲突的情况下简单地替换以前的bean。

从Spring 5.1开始,引入了BeanDefinitionOverrideException以允许开发人员自动抛出异常以防止任何意外的bean覆盖。默认情况下,原始行为仍然可用,这允许覆盖bean。

4. Spring Boot 2.1的配置变更

Spring Boot 2.1默认禁用bean覆盖作为一种防御方法,主要目的是提前注意到重复的bean名称,以防止意外覆盖bean

因此,如果我们的Spring Boot应用依赖于bean覆盖,那么在我们将Spring Boot版本升级到2.1及之后的版本后,很有可能会遇到BeanDefinitionOverrideException。

在接下来的部分中,我们重现一个会发生BeanDefinitionOverrideException的示例,然后我们讨论一些解决方案。

5. 识别冲突的Bean

让我们创建两个不同的Spring配置,每个配置都有一个testBean()方法,以产生BeanDefinitionOverrideException:

@Configuration
public class TestConfiguration1 {

    class TestBean1 {
        private String name;

        // standard getters and setters
    }

    @Bean
    public TestBean1 testBean(){
        return new TestBean1();
    }
}
@Configuration
public class TestConfiguration2 {

    class TestBean2 {
        private String name;

        // standard getters and setters
    }

    @Bean
    public TestBean2 testBean(){
        return new TestBean2();
    }
}

接下来,创建我们的Spring Boot测试类:

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {TestConfiguration1.class, TestConfiguration2.class})
class SpringBootBeanDefinitionOverrideExceptionIntegrationTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    void whenBeanOverridingAllowed_thenTestBean2OverridesTestBean1() {
        Object testBean = applicationContext.getBean("testBean");

        assertThat(testBean.getClass()).isEqualTo(TestConfiguration2.TestBean2.class);
    }
}

运行测试会产生BeanDefinitionOverrideException,但是,该异常为我们提供了一些有用的信息:

Invalid bean definition with name 'testBean' defined in ... 
... cn.tuyucheng.taketoday.beandefinitionoverrideexception.TestConfiguration2 ...
Cannot register bean definition [ ... defined in ... 
... cn.tuyucheng.taketoday.beandefinitionoverrideexception.TestConfiguration2] for bean 'testBean' ...
There is already [ ... defined in ...
... cn.tuyucheng.taketoday.beandefinitionoverrideexception.TestConfiguration1] bound.

请注意,该异常揭示了两条重要信息。

第一个是冲突的bean名称testBean:

Invalid bean definition with name 'testBean' ...

第二个向我们显示了受影响的配置的完整路径:

... cn.tuyucheng.taketoday.beandefinitionoverrideexception.TestConfiguration2 ...
... cn.tuyucheng.taketoday.beandefinitionoverrideexception.TestConfiguration1 ...

结果我们可以看到两个不同的bean被识别为testBean,从而导致冲突。此外,bean包含在配置类TestConfiguration1和TestConfiguration2中。

6. 可能的解决方案

根据我们的配置,Spring Bean具有默认名称,除非我们明确设置它们。

因此,第一个可能的解决方案是重命名我们的bean,在Spring中有一些常见的方法来设置bean名称。

6.1 更改方法名称

默认情况下,Spring将带注解的方法的名称作为bean名称

因此,如果我们在配置类中定义了beans,就像我们的示例一样,那么只需更改方法名称就可以防止BeanDefinitionOverrideException:

@Bean
public TestBean1 testBean1() {
    return new TestBean1();
}
@Bean
public TestBean2 testBean2() {
    return new TestBean2();
}

6.2 @Bean注解

Spring的@Bean注解是一种非常常见的定义bean的方式。

所以另一种选择是设置@Bean注解的name属性:

@Bean("testBean1")
public TestBean1 testBean() {
    return new TestBean1();
}
@Bean("testBean2")
public TestBean1 testBean() {
    return new TestBean2();
}

6.3 原型注解

另一种定义bean的方法是使用构造型注解,启用Spring的@ComponentScan功能后,我们可以使用@Component注解在类级别定义bean名称:

@Component("testBean1")
class TestBean1 {

    private String name;

    // standard getters and setters
}
@Component("testBean2")
class TestBean2 {

    private String name;

    // standard getters and setters
}

6.4 来自第三方库的Bean

在某些情况下,可能会遇到由来自第三方Spring支持的库的bean引起的名称冲突。发生这种情况时,我们应该尝试确定哪个冲突bean属于我们的应用程序,以确定我们是否可以使用上述任何解决方案。但是,如果我们无法更改任何bean定义,那么配置Spring Boot以允许bean覆盖可能是一种解决方法。

要启用bean覆盖,我们需要在application.properties文件中将spring.main.allow-bean-definition-overriding属性设置为true:

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

通过这样做,我们告诉Spring Boot允许bean重写而无需对bean定义进行任何更改。

最后一点,我们应该意识到很难猜测哪个bean具有优先级,因为bean创建顺序是由依赖关系决定的,而依赖关系主要在运行时受到影响。因此,允许覆盖bean会产生意想不到的行为,除非我们足够了解bean的依赖层次结构。

7. 总结

在本文中,我们解释了BeanDefinitionOverrideException在Spring中的含义,为什么它突然出现,以及在Spring Boot 2.1升级后如何解决。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章