Skip to content

Commit e403aef

Browse files
committed
Proper exception in case of an @bean method call encountering a bean type mismatch
Issue: SPR-12905
1 parent 1da98b0 commit e403aef

File tree

2 files changed

+82
-12
lines changed

2 files changed

+82
-12
lines changed

spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import org.springframework.asm.Type;
2828
import org.springframework.beans.factory.BeanFactory;
2929
import org.springframework.beans.factory.BeanFactoryAware;
30+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
31+
import org.springframework.beans.factory.config.BeanDefinition;
3032
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
3133
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
3234
import org.springframework.beans.factory.support.SimpleInstantiationStrategy;
@@ -303,7 +305,7 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object
303305
"result in a failure to process annotations such as @Autowired, " +
304306
"@Resource and @PostConstruct within the method's declaring " +
305307
"@Configuration class. Add the 'static' modifier to this method to avoid " +
306-
"these container lifecycle issues; see @Bean javadoc for complete details",
308+
"these container lifecycle issues; see @Bean javadoc for complete details.",
307309
beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
308310
}
309311
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
@@ -318,8 +320,23 @@ public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object
318320
if (alreadyInCreation) {
319321
beanFactory.setCurrentlyInCreation(beanName, false);
320322
}
321-
return (!ObjectUtils.isEmpty(beanMethodArgs) ?
323+
Object beanInstance = (!ObjectUtils.isEmpty(beanMethodArgs) ?
322324
beanFactory.getBean(beanName, beanMethodArgs) : beanFactory.getBean(beanName));
325+
if (beanInstance != null && !beanMethod.getReturnType().isInstance(beanInstance)) {
326+
String msg = String.format("@Bean method %s.%s called as a bean reference " +
327+
"for type [%s] but overridden by non-compatible bean instance of type [%s].",
328+
beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(),
329+
beanMethod.getReturnType().getName(), beanInstance.getClass().getName());
330+
try {
331+
BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName);
332+
msg += " Overriding bean of same name declared in: " + beanDefinition.getResourceDescription();
333+
}
334+
catch (NoSuchBeanDefinitionException ex) {
335+
// Ignore - simply no detailed message then.
336+
}
337+
throw new IllegalStateException(msg);
338+
}
339+
return beanInstance;
323340
}
324341
finally {
325342
if (alreadyInCreation) {

spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,45 @@ public void configurationClassesProcessedInCorrectOrder() {
298298
beanFactory.registerBeanDefinition("config2", new RootBeanDefinition(SingletonBeanConfig.class));
299299
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
300300
pp.postProcessBeanFactory(beanFactory);
301-
assertTrue(beanFactory.getBean(Foo.class) instanceof ExtendedFoo);
302-
beanFactory.getBean(Bar.class);
301+
302+
Foo foo = beanFactory.getBean(Foo.class);
303+
assertTrue(foo instanceof ExtendedFoo);
304+
Bar bar = beanFactory.getBean(Bar.class);
305+
assertSame(foo, bar.foo);
306+
}
307+
308+
@Test
309+
public void configurationClassesWithValidOverridingForProgrammaticCall() {
310+
beanFactory.registerBeanDefinition("config1", new RootBeanDefinition(OverridingAgainSingletonBeanConfig.class));
311+
beanFactory.registerBeanDefinition("config2", new RootBeanDefinition(OverridingSingletonBeanConfig.class));
312+
beanFactory.registerBeanDefinition("config3", new RootBeanDefinition(SingletonBeanConfig.class));
313+
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
314+
pp.postProcessBeanFactory(beanFactory);
315+
316+
Foo foo = beanFactory.getBean(Foo.class);
317+
assertTrue(foo instanceof ExtendedAgainFoo);
318+
Bar bar = beanFactory.getBean(Bar.class);
319+
assertSame(foo, bar.foo);
320+
}
321+
322+
@Test
323+
public void configurationClassesWithInvalidOverridingForProgrammaticCall() {
324+
beanFactory.registerBeanDefinition("config1", new RootBeanDefinition(InvalidOverridingSingletonBeanConfig.class));
325+
beanFactory.registerBeanDefinition("config2", new RootBeanDefinition(OverridingSingletonBeanConfig.class));
326+
beanFactory.registerBeanDefinition("config3", new RootBeanDefinition(SingletonBeanConfig.class));
327+
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
328+
pp.postProcessBeanFactory(beanFactory);
329+
330+
try {
331+
beanFactory.getBean(Bar.class);
332+
fail("Should have thrown BeanCreationException");
333+
}
334+
catch (BeanCreationException ex) {
335+
assertTrue(ex.getMessage().contains("OverridingSingletonBeanConfig.foo"));
336+
assertTrue(ex.getMessage().contains(ExtendedFoo.class.getName()));
337+
assertTrue(ex.getMessage().contains(Foo.class.getName()));
338+
assertTrue(ex.getMessage().contains("InvalidOverridingSingletonBeanConfig"));
339+
}
303340
}
304341

305342
@Test
@@ -311,6 +348,7 @@ public void scopedProxyTargetMarkedAsNonAutowireCandidate() {
311348
beanFactory.registerBeanDefinition("consumer", new RootBeanDefinition(ScopedProxyConsumer.class));
312349
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
313350
pp.postProcessBeanFactory(beanFactory);
351+
314352
ITestBean injected = beanFactory.getBean("consumer", ScopedProxyConsumer.class).testBean;
315353
assertTrue(injected instanceof ScopedObject);
316354
assertSame(beanFactory.getBean("scopedClass"), injected);
@@ -549,13 +587,11 @@ public void testSingletonArgumentsThroughBeanMethodCall() {
549587
@Order(1)
550588
static class SingletonBeanConfig {
551589

552-
public @Bean
553-
Foo foo() {
590+
public @Bean Foo foo() {
554591
return new Foo();
555592
}
556593

557-
public @Bean
558-
Bar bar() {
594+
public @Bean Bar bar() {
559595
return new Bar(foo());
560596
}
561597
}
@@ -564,23 +600,40 @@ Bar bar() {
564600
@Order(2)
565601
static class OverridingSingletonBeanConfig {
566602

567-
public @Bean
568-
ExtendedFoo foo() {
603+
public @Bean ExtendedFoo foo() {
569604
return new ExtendedFoo();
570605
}
571606

572-
public @Bean
573-
Bar bar() {
607+
public @Bean Bar bar() {
574608
return new Bar(foo());
575609
}
576610
}
577611

612+
@Configuration
613+
static class OverridingAgainSingletonBeanConfig {
614+
615+
public @Bean ExtendedAgainFoo foo() {
616+
return new ExtendedAgainFoo();
617+
}
618+
}
619+
620+
@Configuration
621+
static class InvalidOverridingSingletonBeanConfig {
622+
623+
public @Bean Foo foo() {
624+
return new Foo();
625+
}
626+
}
627+
578628
static class Foo {
579629
}
580630

581631
static class ExtendedFoo extends Foo {
582632
}
583633

634+
static class ExtendedAgainFoo extends ExtendedFoo {
635+
}
636+
584637
static class Bar {
585638

586639
final Foo foo;

0 commit comments

Comments
 (0)