前言
不建议写这么奇葩的代码!!!
这就有点像考试喜欢出的试题,有一堆overload和override的代码,选择题选择调用的是哪个。
不建议写这种让人看着费劲的代码。
问题引出
言归正传,如果有一个这样的配置类,@Bean
注解了相同name = "cupcake"
的bean:
public class BeanOverrideConfig {
@Bean(name = "cupcake")
public Cupcake cupcake1() {
Cupcake cupcake = new Cupcake();
cupcake.setName("Cupcake1");
return cupcake;
}
@Bean(name = "cupcake")
public Cupcake cupcake2() {
Cupcake cupcake = new Cupcake();
cupcake.setName("Cupcake2");
return cupcake;
}
}
下面这个测试类能通过测试吗?注意最后一行代码Assert.assertEquals("Cupcake1", cupcake.getName());
:
public class BeanOverrideTest {
private ApplicationContext ctx = null;
@Before
public void setUp() {
ctx = new AnnotationConfigApplicationContext(BeanOverrideConfig.class);
}
@Test
public void testGetBean() {
Cupcake cupcake = ctx.getBean(Cupcake.class);
Assert.assertNotNull(cupcake);
Assert.assertEquals("Cupcake1", cupcake.getName());
}
}
结果
测试通过!
原因
Spring对configuration class的加载
加载BeanDefinition的过程中有一步:
org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(BeanMethod)
在这个方法中会判断现在的beanName在现有的beanDefinitionMap中是否已存在,然后决定是否覆盖。是否覆盖的策略如下org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.isOverriddenByExistingDefinition(BeanMethod, String)
:
// Is the existing bean definition one that was created from a configuration class?
// -> allow the current bean method to override, since both are at second-pass level.
// However, if the bean method is an overloaded case on the same configuration class,
// preserve the existing bean definition.
if (existingBeanDef instanceof ConfigurationClassBeanDefinition) {
ConfigurationClassBeanDefinition ccbd = (ConfigurationClassBeanDefinition) existingBeanDef;
return ccbd.getMetadata().getClassName().equals(
beanMethod.getConfigurationClass().getMetadata().getClassName());
}
源码里说的很清楚了,如果来自不同层级的bean method,允许覆盖,如果是the same configuration class,preserve the existing bean definition(同一configuration class的overload,保留先前的)。
回到我们的测试类,即保留方法public Cupcake cupcake1()
对应的bean definition,最后测试的时候getName就返回Cupcake1。
深入
如果是下面这种配置和测试:
public class BeanOverrideConfig1 {
@Bean(name = "cupcake")
public Cupcake cupcake1() {
Cupcake cupcake = new Cupcake();
cupcake.setName("Cupcake1");
return cupcake;
}
}
public class BeanOverrideConfig2 {
@Bean(name = "cupcake")
public Cupcake cupcake2() {
Cupcake cupcake = new Cupcake();
cupcake.setName("Cupcake2");
return cupcake;
}
}
public class BeanOverrideTest {
private ApplicationContext ctx = null;
@Before
public void setUp() {
ctx = new AnnotationConfigApplicationContext(BeanOverrideConfig1.class, BeanOverrideConfig2.class);
}
@Test
public void testOverride() {
Cupcake cupcake = ctx.getBean(Cupcake.class);
Assert.assertNotNull(cupcake);
Assert.assertEquals("Cupcake2", cupcake.getName());
}
}
很显然测试能通过,即会覆盖。
@Import呢?
如果是这种情况呢?
@Import(BeanOverrideConfig2.class)
public class BeanOverrideConfig1 {
@Bean(name = "cupcake")
public Cupcake cupcake1() {
Cupcake cupcake = new Cupcake();
cupcake.setName("Cupcake1");
return cupcake;
}
}
public class BeanOverrideConfig2 {
@Bean(name = "cupcake")
public Cupcake cupcake2() {
Cupcake cupcake = new Cupcake();
cupcake.setName("Cupcake2");
return cupcake;
}
}
public class BeanOverrideTest {
private ApplicationContext ctx = null;
@Before
public void setUp() {
ctx = new AnnotationConfigApplicationContext(BeanOverrideConfig1.class);
}
@Test
public void testOverride() {
Cupcake cupcake = ctx.getBean(Cupcake.class);
Assert.assertNotNull(cupcake);
Assert.assertEquals("Cupcake1", cupcake.getName());
}
}
测试通过,这种@Import的情况也没认为是同一配置类,不会覆盖。