diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index d35ecd062b..683e62c071 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -309,6 +309,7 @@ class ConfigurationClassEnhancer { * super implementation of the proxied method i.e., the actual {@code @Bean} method */ @Override + @Nullable public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs, MethodProxy cglibMethodProxy) throws Throwable { @@ -360,10 +361,11 @@ class ConfigurationClassEnhancer { return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs); } - return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName); + return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName); } - private Object obtainBeanInstanceFromFactory(Method beanMethod, Object[] beanMethodArgs, + @Nullable + private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs, ConfigurableBeanFactory beanFactory, String beanName) { // The user (i.e. not the factory) is requesting this bean through a call to @@ -390,18 +392,29 @@ class ConfigurationClassEnhancer { Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) : beanFactory.getBean(beanName)); if (!ClassUtils.isAssignableValue(beanMethod.getReturnType(), beanInstance)) { - String msg = String.format("@Bean method %s.%s called as a bean reference " + + if (beanInstance.equals(null)) { + if (logger.isDebugEnabled()) { + logger.debug(String.format("@Bean method %s.%s called as bean reference " + + "for type [%s] returned null bean; resolving to null value.", + beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(), + beanMethod.getReturnType().getName())); + } + beanInstance = null; + } + else { + String msg = String.format("@Bean method %s.%s called as bean reference " + "for type [%s] but overridden by non-compatible bean instance of type [%s].", beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName(), beanMethod.getReturnType().getName(), beanInstance.getClass().getName()); - try { - BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName); - msg += " Overriding bean of same name declared in: " + beanDefinition.getResourceDescription(); + try { + BeanDefinition beanDefinition = beanFactory.getMergedBeanDefinition(beanName); + msg += " Overriding bean of same name declared in: " + beanDefinition.getResourceDescription(); + } + catch (NoSuchBeanDefinitionException ex) { + // Ignore - simply no detailed message then. + } + throw new IllegalStateException(msg); } - catch (NoSuchBeanDefinitionException ex) { - // Ignore - simply no detailed message then. - } - throw new IllegalStateException(msg); } Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod(); if (currentlyInvoked != null) { diff --git a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java index 3eb49de159..e77f1fd3d7 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/configuration/ConfigurationClassProcessingTests.java @@ -205,6 +205,15 @@ public class ConfigurationClassProcessingTests { assertNotSame(bar.getSpouse(), baz); } + @Test + public void configurationWithNullReference() { + BeanFactory factory = initBeanFactory(ConfigWithNullReference.class); + + TestBean foo = factory.getBean("foo", TestBean.class); + assertTrue(factory.getBean("bar").equals(null)); + assertNull(foo.getSpouse()); + } + @Test public void configurationWithAdaptivePrototypes() { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); @@ -307,7 +316,7 @@ public class ConfigurationClassProcessingTests { static TestBean testBean = new TestBean(ConfigWithBeanWithAliases.class.getSimpleName()); - @Bean(name = { "name1", "alias1", "alias2", "alias3" }) + @Bean(name = {"name1", "alias1", "alias2", "alias3"}) public TestBean methodName() { return testBean; } @@ -319,7 +328,7 @@ public class ConfigurationClassProcessingTests { static TestBean testBean = new TestBean(ConfigWithBeanWithAliasesConfiguredViaValueAttribute.class.getSimpleName()); - @Bean({ "enigma", "alias1", "alias2", "alias3" }) + @Bean({"enigma", "alias1", "alias2", "alias3"}) public TestBean methodName() { return testBean; } @@ -415,6 +424,16 @@ public class ConfigurationClassProcessingTests { } + @Configuration + static class ConfigWithNullReference extends ConfigWithPrototypeBean { + + @Override + public TestBean bar() { + return null; + } + } + + @Scope("prototype") static class AdaptiveInjectionPoints {