SpringBoot相同名称bean的覆盖问题



SpringBoot 2.0或更早版本

在SpringBoot 2.0或更早版本中,默认允许bean定义被覆盖。并且没有提供通过属性配置的方式来禁止这种行为,设置spring.main.allow-bean-definition-overriding=false并不会生效,因为它是2.1.0版本开始引入的。

我们可以通过编程的方式,在启动类中创建一个新的ApplicationContextInitializer,并且重写它的initialize()方法以禁用bean定义覆盖。

public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(DemoApplication.class);
        application.addInitializers((ApplicationContextInitializer<GenericApplicationContext>) applicationContext ->
                applicationContext.setAllowBeanDefinitionOverriding(false)
        );
        application.run(args);
    }
}

SpringBoot 2.1.0及后续版本

从SpringBoot 2.1.0开始,默认禁用bean名称相同时的覆盖行为。

可以通过配置以下属性重新启用(不推荐):

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

备注

  • @Primary注解无法适用的情况

如果有多个名称相同的bean时,添加@Primary注解的方式无法解决bean覆盖的问题。@Primary适用于存在多个类型bean的情况,它会将其中一个bean指定为依赖注入中优先提供的bean。

  • 自动配置条件注入的情况

许多框架会自动配置默认的bean,同时设置为条件注入,因此即使创建名称相同的bean,也不存在覆盖的问题。例如在Spring JacksonAutoConfiguration源码中,配置ObjectMapperbean的代码如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jackson2ObjectMapperBuilder.class)
static class JacksonObjectMapperConfiguration {

    @Bean
    @Primary
    @ConditionalOnMissingBean
    ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        return builder.createXmlMapper(false).build();
    }

}

使用了@ConditionalOnMissingBean注解,表示只有在容器中不存在ObjectMapper类型的bean时才会创建该bean。

总结

当设置允许bean定义被覆盖时,哪个bean会被最终提供是难以预测的,因为bean的创建顺序主要由运行时的依赖关系决定的,允许bean重写可能会产生意料之外的行为,除非足够了解bean的依赖层次结构。