每天学习一点点之 Spring Boot 1.x 升级 2.x 之 allowBeanDefinitionOverriding

发布时间:2024年01月10日

最近组内大佬正在进行 Spring Boot 版本的升级,从 1.x 版本升级到 2.x 版本。在查看代码变更时,我注意到我之前编写的一个名为 ShardingRuleStrategy 的类被添加了 @Primary 注解。这个类在原来的代码中被标记为 @Component,同时也在 API 中被定义:

@Bean
public ShardingRuleStrategy shardingRuleStrategy() {
  	return new ShardingRuleStrategy(shardingDataSourcePropertiesResolver.getMetaData());
}

这就引发了一个问题:在 Spring Boot 1.x 版本中,定义两个同名的 Bean 是被允许的,不会引发任何冲突。但是在 Spring Boot 2.x 版本中,这样做就会导致冲突。

那这究竟是怎么回事呢?

复现

首先需要创建一个 Spring Boot 1.x 的项目。由于 Spring Boot 正在快速迭代,无法直接使用 [https://start.spring.io/](vscode-file://vscode-app/Applications/Visual Studio Code.app/Contents/Resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 来创建 1.x 版本的项目,因此需要手动进行构建。

构建完成后,启动项目,可以在控制台看到以下日志输出:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v1.5.22.RELEASE)

定义 Bean:

@Component
public class Test {
}
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(App.class, args);
        String[] beanNames = context.getBeanDefinitionNames();
        Test bean = context.getBean(Test.class);
        System.out.println(bean);
        System.out.println("List :");
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }
    }

    @Bean
    public Test test(){
        return new Test();
    }
}

运行后输出:

demo.sb1.Test@61d9efe0
List :
...
test
...

可以看到,Spring 容器中只有一个 Test Bean,没有出现任何异常。

接下来需要创建一个 Spring Boot 2.x 的项目。只需要在 pom.xml 文件中修改 Spring Boot 的版本即可:

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.3</version>
   <!-- <version>1.5.22.RELEASE</version>-->
  </parent>

重新启动工程,发现报错了:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.3)
...
***************************
APPLICATION FAILED TO START
***************************

Description:

The bean 'test', defined in demo.sb1.App, could not be registered. A bean with that name has already been defined in file [/Users/dongguabai/Downloads/demosb1/target/classes/demo/sb1/Test.class] and overriding is disabled.

Action:

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

这意味着名为 ‘test’ 的 Bean 已经在 demo.sb1.App 中定义,并且不允许被覆盖。Spring Boot 也给出了修复这个问题的方法,即通过设置 spring.main.allow-bean-definition-overriding 配置项。这个配置项就是接下来要讨论的主题。

allowBeanDefinitionOverriding 配置

可以看到,这个配置项的前缀是 spring.main,这是用来控制 Spring Boot 应用的主要行为的配置项。

其中最经典的一个例子就是 spring.main.banner-mode,它可以用来控制 Spring Boot 在启动时是否显示 banner。如果想了解更多的配置项,可以在 SpringApplication 类中进行查找。

接下来看一下 spring.main.allow-bean-definition-overriding 配置项的实现。可以在 org.springframework.boot.SpringApplication#prepareContext 方法中找到相关的代码:

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		...
		if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
			((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
			if (beanFactory instanceof DefaultListableBeanFactory) {
        //为DefaultListableBeanFactory设置allowBeanDefinitionOverriding属性
				((DefaultListableBeanFactory) beanFactory)
						.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
			}
		}
		if (this.lazyInitialization) {
			context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
		}
...
}

可以看到就是基于 DefaultListableBeanFactoryallowBeanDefinitionOverriding 属性实现的。

版本疑问

可以看到,spring.main.allow-bean-definition-overriding 配置项是基于 DefaultListableBeanFactoryallowBeanDefinitionOverriding 属性实现的。

然而 DefaultListableBeanFactory 是属于 Spring 的一部分,而 allowBeanDefinitionOverriding 属性在 Spring 的早期版本中就已经存在了。Spring Boot 2.x 需要至少 Spring Framework 5.0 版本。这意味着,Spring Boot 1.x 实际上也可以实现这个属性对应的功能,只不过 Spring Boot 官方没有提供现成的配置项而已

那就基于 Spring Boot 1.x 的版本实现一下,在启动类加一个自己定义的 ApplicationContextInitializer,并且设置 allowBeanDefinitionOverriding 属性为 false

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(App.class);
        application.addInitializers(new MyApplicationContextInitializer());
        ApplicationContext context = application.run(args);
        String[] beanNames = context.getBeanDefinitionNames();
        Test bean = context.getBean(Test.class);
        System.out.println(bean);
        System.out.println("List :");
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }
    }

    static class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            if (applicationContext.getBeanFactory() instanceof DefaultListableBeanFactory) {
                ((DefaultListableBeanFactory) applicationContext.getBeanFactory()).setAllowBeanDefinitionOverriding(false);
            }
        }

        @Bean
        public Test test() {
            return new Test();
        }
    }
}

启动:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v1.5.22.RELEASE)
...
org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'test' defined in demo.sb1.App$CustomApplicationContextInitializer: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=demo.sb1.App$CustomApplicationContextInitializer; factoryMethodName=test; initMethodName=null; destroyMethodName=(inferred); defined in demo.sb1.App$CustomApplicationContextInitializer] for bean 'test': There is already [Generic bean: class [demo.sb1.Test]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [/Users/dongguabai/Downloads/demosb1/target/classes/demo/sb1/Test.class]] bound.
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.registerBeanDefinition(DefaultListableBeanFactory.java:807) ~[spring-beans-4.3.25.RELEASE.jar:4.3.25.RELEASE]...

可以看到出现了不允许 Bean 被重复定义的异常。

总结

本文对 Spring Boot 2.x 中新增的 spring.main.allow-bean-definition-overriding 配置进行了简要分析,并通过实际示例说明了这个配置的底层原理并非源自 Spring Boot 2.x。实际上,即使在 Spring Boot 1.x 中,我们也可以实现这个功能,只是 Spring Boot 官方并没有提供现成的配置项。

欢迎关注公众号:
在这里插入图片描述

文章来源:https://blog.csdn.net/Dongguabai/article/details/135513392
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。