diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index c112676ea1..9d11eeb520 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -586,9 +586,10 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; - Class processedType = ibp.predictBeanType(beanClass, beanName); - if (processedType != null) { - return processedType; + Class predictedType = ibp.predictBeanType(beanClass, beanName); + if (predictedType != null && (typesToMatch.length > 1 || + !FactoryBean.class.equals(typesToMatch[0]) || FactoryBean.class.isAssignableFrom(predictedType))) { + return predictedType; } } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 6ec60e6e61..9e97ba2faf 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -497,18 +497,21 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp // Retrieve corresponding bean definition. RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); + Class[] typesToMatch = (FactoryBean.class.equals(typeToMatch) ? + new Class[] {typeToMatch} : new Class[] {FactoryBean.class, typeToMatch}); + // Check decorated bean definition, if any: We assume it'll be easier // to determine the decorated bean's type than the proxy's type. BeanDefinitionHolder dbd = mbd.getDecoratedDefinition(); if (dbd != null && !BeanFactoryUtils.isFactoryDereference(name)) { RootBeanDefinition tbd = getMergedBeanDefinition(dbd.getBeanName(), dbd.getBeanDefinition(), mbd); - Class targetClass = predictBeanType(dbd.getBeanName(), tbd, FactoryBean.class, typeToMatch); + Class targetClass = predictBeanType(dbd.getBeanName(), tbd, typesToMatch); if (targetClass != null && !FactoryBean.class.isAssignableFrom(targetClass)) { return typeToMatch.isAssignableFrom(targetClass); } } - Class beanClass = predictBeanType(beanName, mbd, FactoryBean.class, typeToMatch); + Class beanClass = predictBeanType(beanName, mbd, typesToMatch); if (beanClass == null) { return false; } @@ -1332,9 +1335,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp * @param mbd the corresponding bean definition */ protected boolean isFactoryBean(String beanName, RootBeanDefinition mbd) { - Class predictedType = predictBeanType(beanName, mbd, FactoryBean.class); - return (predictedType != null && FactoryBean.class.isAssignableFrom(predictedType)) || - (mbd.hasBeanClass() && FactoryBean.class.isAssignableFrom(mbd.getBeanClass())); + Class beanClass = predictBeanType(beanName, mbd, FactoryBean.class); + return (beanClass != null && FactoryBean.class.isAssignableFrom(beanClass)); } /** diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/Spr8954Tests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/Spr8954Tests.java index 5c1368c461..411f538045 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/Spr8954Tests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/Spr8954Tests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,8 @@ import static org.junit.Assert.*; import java.util.Map; import org.junit.Test; + +import org.springframework.beans.BeansException; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; import org.springframework.beans.factory.support.DefaultListableBeanFactory; @@ -49,6 +51,8 @@ public class Spr8954Tests { assertThat(bf.getBean("foo"), instanceOf(Foo.class)); assertThat(bf.getBean("&foo"), instanceOf(FooFactoryBean.class)); + assertThat(bf.isTypeMatch("&foo", FactoryBean.class), is(true)); + @SuppressWarnings("rawtypes") Map fbBeans = bf.getBeansOfType(FactoryBean.class); assertThat(1, equalTo(fbBeans.size())); @@ -59,6 +63,25 @@ public class Spr8954Tests { assertThat("&foo", equalTo(aiBeans.keySet().iterator().next())); } + @Test + public void findsBeansByTypeIfNotInstantiated() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.registerBeanDefinition("foo", new RootBeanDefinition(FooFactoryBean.class)); + bf.addBeanPostProcessor(new PredictingBPP()); + + assertThat(bf.isTypeMatch("&foo", FactoryBean.class), is(true)); + + @SuppressWarnings("rawtypes") + Map fbBeans = bf.getBeansOfType(FactoryBean.class); + assertThat(1, equalTo(fbBeans.size())); + assertThat("&foo", equalTo(fbBeans.keySet().iterator().next())); + + Map aiBeans = bf.getBeansOfType(AnInterface.class); + assertThat(1, equalTo(aiBeans.size())); + assertThat("&foo", equalTo(aiBeans.keySet().iterator().next())); + } + + static class FooFactoryBean implements FactoryBean, AnInterface { @Override @@ -84,7 +107,9 @@ public class Spr8954Tests { } interface PredictedType { + } + static class PredictedTypeImpl implements PredictedType { } static class PredictingBPP extends InstantiationAwareBeanPostProcessorAdapter { @@ -92,8 +117,8 @@ public class Spr8954Tests { @Override public Class predictBeanType(Class beanClass, String beanName) { return FactoryBean.class.isAssignableFrom(beanClass) ? - PredictedType.class : - super.predictBeanType(beanClass, beanName); + PredictedType.class : null; } } + } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassSpr8954Tests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassSpr8954Tests.java new file mode 100644 index 0000000000..1a7a5b1b33 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassSpr8954Tests.java @@ -0,0 +1,132 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.annotation; + +import java.util.Map; + +import org.junit.Test; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +/** + * Unit tests for SPR-8954, in which a custom {@link InstantiationAwareBeanPostProcessor} + * forces the predicted type of a FactoryBean, effectively preventing retrieval of the + * bean from calls to #getBeansOfType(FactoryBean.class). The implementation of + * {@link AbstractBeanFactory#isFactoryBean(String, RootBeanDefinition)} now ensures + * that not only the predicted bean type is considered, but also the original bean + * definition's beanClass. + * + * @author Chris Beams + * @author Oliver Gierke + */ +public class ConfigurationClassSpr8954Tests { + + @Test + public void repro() { + AnnotationConfigApplicationContext bf = new AnnotationConfigApplicationContext(); + bf.registerBeanDefinition("fooConfig", new RootBeanDefinition(FooConfig.class)); + bf.getBeanFactory().addBeanPostProcessor(new PredictingBPP()); + bf.refresh(); + + assertThat(bf.getBean("foo"), instanceOf(Foo.class)); + assertThat(bf.getBean("&foo"), instanceOf(FooFactoryBean.class)); + + assertThat(bf.isTypeMatch("&foo", FactoryBean.class), is(true)); + + @SuppressWarnings("rawtypes") + Map fbBeans = bf.getBeansOfType(FactoryBean.class); + assertThat(1, equalTo(fbBeans.size())); + assertThat("&foo", equalTo(fbBeans.keySet().iterator().next())); + + Map aiBeans = bf.getBeansOfType(AnInterface.class); + assertThat(1, equalTo(aiBeans.size())); + assertThat("&foo", equalTo(aiBeans.keySet().iterator().next())); + } + + @Test + public void findsBeansByTypeIfNotInstantiated() { + AnnotationConfigApplicationContext bf = new AnnotationConfigApplicationContext(); + bf.registerBeanDefinition("fooConfig", new RootBeanDefinition(FooConfig.class)); + bf.getBeanFactory().addBeanPostProcessor(new PredictingBPP()); + bf.refresh(); + + assertThat(bf.isTypeMatch("&foo", FactoryBean.class), is(true)); + + @SuppressWarnings("rawtypes") + Map fbBeans = bf.getBeansOfType(FactoryBean.class); + assertThat(1, equalTo(fbBeans.size())); + assertThat("&foo", equalTo(fbBeans.keySet().iterator().next())); + + Map aiBeans = bf.getBeansOfType(AnInterface.class); + assertThat(1, equalTo(aiBeans.size())); + assertThat("&foo", equalTo(aiBeans.keySet().iterator().next())); + } + + + static class FooConfig { + + @Bean FooFactoryBean foo() { + return new FooFactoryBean(); + } + } + + static class FooFactoryBean implements FactoryBean, AnInterface { + + @Override + public Foo getObject() throws Exception { + return new Foo(); + } + + @Override + public Class getObjectType() { + return Foo.class; + } + + @Override + public boolean isSingleton() { + return true; + } + } + + interface AnInterface { + } + + static class Foo { + } + + interface PredictedType { + } + + static class PredictedTypeImpl implements PredictedType { + } + + static class PredictingBPP extends InstantiationAwareBeanPostProcessorAdapter { + + @Override + public Class predictBeanType(Class beanClass, String beanName) { + return FactoryBean.class.isAssignableFrom(beanClass) ? + PredictedType.class : null; + } + } + +}