细说SpringBootBean定义覆盖机制

印象中Spring Boot 2.x中的bean定义是不能重复的,如下demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@SpringBootApplication
public class BeanDemoApplication {
public static void main(String[] args) {
SpringApplication.run(BeanDemoApplication.class, args);
}

}

@Configuration
class Bean1Config {

@Bean
public String bean() {
return "BEAN1";
}
}

@Configuration
class Bean2Config {

@Bean
public String bean() {
return "BEAN2";
}
}

果然启动是报错的,提示需要设置 spring.main.allow-bean-definition-overriding=true表示后发现的 bean 会覆盖之前相同名称的bean,即使我们在其中一个Bean 上加上 @Primary 也是不行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
***************************
APPLICATION FAILED TO START
***************************

Description:

The bean 'bean', defined in class path resource [com/example/beandemo/Bean2Config.class], could not be registered. A bean with that name has already been defined in class path resource [com/example/beandemo/Bean1Config.class] and overriding is disabled.

Action:

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


Process finished with exit code 1

让我们来回顾一下spring bean 的注册过程,bean加载到spring工程后,会存储在beanDefinitionMap,key是bean的名称,可查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
DefaultListableBeanFactory 
// DefaultListableBeanFactory:147
/** Whether to allow re-registration of a different definition with the same name. */
private boolean allowBeanDefinitionOverriding = true;
// 这里明确说明Spring默认是允许重复定义的

/**
* Set whether it should be allowed to override bean definitions by registering
* a different definition with the same name, automatically replacing the former.
* If not, an exception will be thrown. This also applies to overriding aliases.
* <p>Default is "true".
* @see #registerBeanDefinition
*/
// DefaultListableBeanFactory:236
public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) {
this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding;
}

// DefaultListableBeanFactory:987
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
...
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
//如果不允许相同名称的bean存在,则直接抛出异常
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
...
this.beanDefinitionMap.put(beanName, beanDefinition);
}
...
}

从上述源码中我们可以看到,默认就是允许重复定义的,可为什么还要手动设置?
原因就是上面代码是spring 的代码,springboot 对这个参数进行了二次封装,springboot 中的 allowBeanDefinitionOverriding 是没有初始化默认值的,我们知道,java中的boolean类型不初始化是false。
SpringApplication 源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//SpringApplication:235
private boolean allowBeanDefinitionOverriding;

//SpringApplication:931
/**
* Sets if bean definition overriding, by registering a definition with the same name
* as an existing definition, should be allowed. Defaults to {@code false}.
* @param allowBeanDefinitionOverriding if overriding is allowed
* @since 2.1.0
* @see DefaultListableBeanFactory#setAllowBeanDefinitionOverriding(boolean)
*/
public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding) {
this.allowBeanDefinitionOverriding = allowBeanDefinitionOverriding;
}

//SpringApplication:361
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment);
postProcessApplicationContext(context);
applyInitializers(context);
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
// 在此处给bean工程设置属性
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}

`

那么到底allowBeanDefinitionOverriding应该设置true还是false?
上面代码中可以看到,spring中默认是true,也就是默认支持名称相同的bean的覆盖。而springboot中的默认值是false,也就是不支持名称相同的bean被覆盖。
那么应该如何选择呢?
这里笔者认为默认不覆盖比较好。
因为还是一个系统中尽量不要存在名称相同的bean,否则后者覆盖前者,多人分工合作的时候,难以避免某些bean被覆盖,会出现很多诡异的问题 ,甚至会带来线上真实的业务损失。
bean的名称不相同,依据具体的业务给bean起名字。这样不但可以解决bean名称重复的问题,还可以大大提高程序的可读性与可维护性。
但当集成了第三方的库,不同库直接由于是多个团队开发的,有可能会出现bean名称相同的情况。这种情况其实也可以通过设置 @ComponentScan(excludeFilters = xxx) 来解决,如demo中,可如下设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@SpringBootApplication
@ComponentScan(excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = Bean2Config.class))
public class BeanDemoApplication {
public static void main(String[] args) {
SpringApplication.run(BeanDemoApplication.class, args);
}

}

@Configuration
class Bean1Config {

@Bean
public String bean() {
return "BEAN1";
}
}

@Configuration
class Bean2Config {

@Bean
public String bean() {
return "BEAN2";
}
}