diff --git a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index af81eeb46ba..1a4c9874369 100644 --- a/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/org.springframework.beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -21,6 +21,9 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; @@ -68,6 +71,7 @@ import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.core.GenericTypeResolver; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; @@ -651,7 +655,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } /** - * This implementation checks the FactoryBean's getObjectType method + * This implementation attempts to query the FactoryBean's generic parameter metadata + * if present to determin the object type. If not present, i.e. the FactoryBean is + * declared as a raw type, checks the FactoryBean's getObjectType method * on a plain instance of the FactoryBean, without bean properties applied yet. * If this doesn't return a type yet, a full creation of the FactoryBean is * used as fallback (through delegation to the superclass's implementation). @@ -660,14 +666,34 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac * it will be fully created to check the type of its exposed object. */ @Override - protected Class getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) { - FactoryBean fb = (mbd.isSingleton() ? + protected Class getTypeForFactoryBean(String beanName, RootBeanDefinition mbd) { + Class objectType = null; + String factoryBeanName = mbd.getFactoryBeanName(); + String factoryMethodName = mbd.getFactoryMethodName(); + if (factoryBeanName != null && factoryMethodName != null) { + // Try to obtain the FactoryBean's object type without instantiating it at all. + BeanDefinition fbDef = getBeanDefinition(factoryBeanName); + if (fbDef instanceof AbstractBeanDefinition) { + Class fbClass = ((AbstractBeanDefinition)fbDef).getBeanClass(); + if (ClassUtils.isCglibProxyClass(fbClass)) { + // CGLIB subclass methods hide generic parameters. look at the superclass. + fbClass = fbClass.getSuperclass(); + } + Method m = ReflectionUtils.findMethod(fbClass, factoryMethodName); + objectType = GenericTypeResolver.resolveReturnTypeArgument(m, FactoryBean.class); + if (objectType != null) { + return objectType; + } + } + } + + FactoryBean fb = (mbd.isSingleton() ? getSingletonFactoryBeanForTypeCheck(beanName, mbd) : getNonSingletonFactoryBeanForTypeCheck(beanName, mbd)); if (fb != null) { // Try to obtain the FactoryBean's object type from this early stage of the instance. - Class objectType = getTypeForFactoryBean(fb); + objectType = getTypeForFactoryBean(fb); if (objectType != null) { return objectType; } diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/ConfigurationWithFactoryBeanAndAutowiringTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/ConfigurationWithFactoryBeanAndAutowiringTests.java new file mode 100755 index 00000000000..14d1932dca6 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/ConfigurationWithFactoryBeanAndAutowiringTests.java @@ -0,0 +1,218 @@ +/* + * Copyright 2002-2011 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 org.junit.Test; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.util.Assert; + +/** + * Tests cornering bug SPR-8514. + * + * @author Chris Beams + * @since 3.1 + */ +public class ConfigurationWithFactoryBeanAndAutowiringTests { + + @Test + public void withConcreteFactoryBeanImplementationAsReturnType() { + AnnotationConfigApplicationContext ctx = + new AnnotationConfigApplicationContext(); + ctx.register(AppConfig.class); + ctx.register(ConcreteFactoryBeanImplementationConfig.class); + ctx.refresh(); + } + + @Test + public void withParameterizedFactoryBeanImplementationAsReturnType() { + AnnotationConfigApplicationContext ctx = + new AnnotationConfigApplicationContext(); + ctx.register(AppConfig.class); + ctx.register(ParameterizedFactoryBeanImplementationConfig.class); + ctx.refresh(); + } + + @Test + public void withParameterizedFactoryBeanInterfaceAsReturnType() { + AnnotationConfigApplicationContext ctx = + new AnnotationConfigApplicationContext(); + ctx.register(AppConfig.class); + ctx.register(ParameterizedFactoryBeanInterfaceConfig.class); + ctx.refresh(); + } + + @Test + public void withNonPublicParameterizedFactoryBeanInterfaceAsReturnType() { + AnnotationConfigApplicationContext ctx = + new AnnotationConfigApplicationContext(); + ctx.register(AppConfig.class); + ctx.register(NonPublicParameterizedFactoryBeanInterfaceConfig.class); + ctx.refresh(); + } + + @Test(expected=BeanCreationException.class) + public void withRawFactoryBeanInterfaceAsReturnType() { + AnnotationConfigApplicationContext ctx = + new AnnotationConfigApplicationContext(); + ctx.register(AppConfig.class); + ctx.register(RawFactoryBeanInterfaceConfig.class); + ctx.refresh(); + } + + @Test(expected=BeanCreationException.class) + public void withWildcardParameterizedFactoryBeanInterfaceAsReturnType() { + AnnotationConfigApplicationContext ctx = + new AnnotationConfigApplicationContext(); + ctx.register(AppConfig.class); + ctx.register(WildcardParameterizedFactoryBeanInterfaceConfig.class); + ctx.refresh(); + } + +} + + +class DummyBean { +} + + +class MyFactoryBean implements FactoryBean { + public String getObject() throws Exception { + return "foo"; + } + public Class getObjectType() { + return String.class; + } + public boolean isSingleton() { + return true; + } +} + + +class MyParameterizedFactoryBean implements FactoryBean { + + private final T obj; + + public MyParameterizedFactoryBean(T obj) { + this.obj = obj; + } + + public T getObject() throws Exception { + return obj; + } + + @SuppressWarnings("unchecked") + public Class getObjectType() { + return (Class)obj.getClass(); + } + + public boolean isSingleton() { + return true; + } +} + + +@Configuration +class AppConfig { + @Bean + public DummyBean dummyBean() { + return new DummyBean(); + } +} + + +@Configuration +class ConcreteFactoryBeanImplementationConfig { + @Autowired + private DummyBean dummyBean; + + @Bean + public MyFactoryBean factoryBean() { + Assert.notNull(dummyBean, "DummyBean was not injected."); + return new MyFactoryBean(); + } +} + + +@Configuration +class ParameterizedFactoryBeanImplementationConfig { + @Autowired + private DummyBean dummyBean; + + @Bean + public MyParameterizedFactoryBean factoryBean() { + Assert.notNull(dummyBean, "DummyBean was not injected."); + return new MyParameterizedFactoryBean("whatev"); + } +} + + +@Configuration +class ParameterizedFactoryBeanInterfaceConfig { + @Autowired + private DummyBean dummyBean; + + @Bean + public FactoryBean factoryBean() { + Assert.notNull(dummyBean, "DummyBean was not injected."); + return new MyFactoryBean(); + } +} + + +@Configuration +class NonPublicParameterizedFactoryBeanInterfaceConfig { + @Autowired + private DummyBean dummyBean; + + @Bean + FactoryBean factoryBean() { + Assert.notNull(dummyBean, "DummyBean was not injected."); + return new MyFactoryBean(); + } +} + + +@Configuration +class RawFactoryBeanInterfaceConfig { + @Autowired + private DummyBean dummyBean; + + @Bean + @SuppressWarnings("rawtypes") + public FactoryBean factoryBean() { + Assert.notNull(dummyBean, "DummyBean was not injected."); + return new MyFactoryBean(); + } +} + + +@Configuration +class WildcardParameterizedFactoryBeanInterfaceConfig { + @Autowired + private DummyBean dummyBean; + + @Bean + public FactoryBean factoryBean() { + Assert.notNull(dummyBean, "DummyBean was not injected."); + return new MyFactoryBean(); + } +}