diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java b/spring-beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java index 31d2cfbe3bb..c02210581b6 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java @@ -127,7 +127,7 @@ public class NoSuchBeanDefinitionException extends BeansException { * that failed. */ public Class getBeanType() { - return (this.resolvableType != null ? this.resolvableType.getRawClass() : null); + return (this.resolvableType != null ? this.resolvableType.resolve() : null); } /** 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 69b3d93a062..1884912e652 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 @@ -74,6 +74,7 @@ import org.springframework.core.GenericTypeResolver; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.PriorityOrdered; +import org.springframework.core.ResolvableType; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; @@ -659,9 +660,9 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac * @see #createBean */ protected Class getTypeForFactoryMethod(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { - Class preResolved = mbd.resolvedFactoryMethodReturnType; - if (preResolved != null) { - return preResolved; + ResolvableType cachedReturnType = mbd.factoryMethodReturnType; + if (cachedReturnType != null) { + return cachedReturnType.resolve(); } Class factoryClass; @@ -685,11 +686,12 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac if (factoryClass == null) { return null; } + factoryClass = ClassUtils.getUserClass(factoryClass); // If all factory methods have the same return type, return that type. // Can't clearly figure out exact method due to type converting / autowiring! Class commonType = null; - boolean cache = false; + Method uniqueCandidate = null; int minNrOfArgs = mbd.getConstructorArgumentValues().getArgumentCount(); Method[] candidates = ReflectionUtils.getUniqueDeclaredMethods(factoryClass); for (Method factoryMethod : candidates) { @@ -724,8 +726,12 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac Class returnType = AutowireUtils.resolveReturnTypeForFactoryMethod( factoryMethod, args, getBeanClassLoader()); if (returnType != null) { - cache = true; + uniqueCandidate = (commonType == null ? factoryMethod : null); commonType = ClassUtils.determineCommonAncestor(returnType, commonType); + if (commonType == null) { + // Ambiguous return types found: return null to indicate "not determinable". + return null; + } } } catch (Throwable ex) { @@ -735,22 +741,22 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac } } else { + uniqueCandidate = (commonType == null ? factoryMethod : null); commonType = ClassUtils.determineCommonAncestor(factoryMethod.getReturnType(), commonType); + if (commonType == null) { + // Ambiguous return types found: return null to indicate "not determinable". + return null; + } } } } if (commonType != null) { // Clear return type found: all factory methods return same type. - if (cache) { - mbd.resolvedFactoryMethodReturnType = commonType; - } - return commonType; - } - else { - // Ambiguous return types found: return null to indicate "not determinable". - return null; + mbd.factoryMethodReturnType = (uniqueCandidate != null ? + ResolvableType.forMethodReturnType(uniqueCandidate) : ResolvableType.forClass(commonType)); } + return commonType; } /** 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 6b034851b38..326c3295be0 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 @@ -517,7 +517,10 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp // Retrieve corresponding bean definition. RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); - Class classToMatch = typeToMatch.getRawClass(); + Class classToMatch = typeToMatch.resolve(); + if (classToMatch == null) { + classToMatch = FactoryBean.class; + } Class[] typesToMatch = (FactoryBean.class == classToMatch ? new Class[] {classToMatch} : new Class[] {FactoryBean.class, classToMatch}); @@ -557,6 +560,13 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp } } + ResolvableType resolvableType = mbd.targetType; + if (resolvableType == null) { + resolvableType = mbd.factoryMethodReturnType; + } + if (resolvableType != null && resolvableType.resolve() == beanType) { + return typeToMatch.isAssignableFrom(resolvableType); + } return typeToMatch.isAssignableFrom(beanType); } } @@ -1447,6 +1457,10 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp * @return the type of the bean, or {@code null} if not predictable */ protected Class predictBeanType(String beanName, RootBeanDefinition mbd, Class... typesToMatch) { + Class targetType = mbd.getTargetType(); + if (targetType != null) { + return targetType; + } if (mbd.getFactoryMethodName() != null) { return null; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java index 1bf54034558..0b650df8232 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 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. @@ -162,8 +162,8 @@ abstract class AutowireUtils { * Determine the target type for the generic return type of the given * generic factory method, where formal type variables are declared * on the given method itself. - *

For example, given a factory method with the following signature, - * if {@code resolveReturnTypeForFactoryMethod()} is invoked with the reflected + *

For example, given a factory method with the following signature, if + * {@code resolveReturnTypeForFactoryMethod()} is invoked with the reflected * method for {@code creatProxy()} and an {@code Object[]} array containing * {@code MyService.class}, {@code resolveReturnTypeForFactoryMethod()} will * infer that the target return type is {@code MyService}. @@ -184,9 +184,9 @@ abstract class AutowireUtils { * @param method the method to introspect (never {@code null}) * @param args the arguments that will be supplied to the method when it is * invoked (never {@code null}) - * @param classLoader the ClassLoader to resolve class names against, if necessary - * (never {@code null}) - * @return the resolved target return type, the standard return type, or {@code null} + * @param classLoader the ClassLoader to resolve class names against, + * if necessary (never {@code null}) + * @return the resolved target return type or the standard method return type * @since 3.2.5 */ public static Class resolveReturnTypeForFactoryMethod(Method method, Object[] args, ClassLoader classLoader) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java index cba9e9770ab..b204d551c3c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java @@ -147,22 +147,23 @@ public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandid protected ResolvableType getReturnTypeForFactoryMethod(RootBeanDefinition rbd, DependencyDescriptor descriptor) { // Should typically be set for any kind of factory method, since the BeanFactory // pre-resolves them before reaching out to the AutowireCandidateResolver... - Class preResolved = rbd.resolvedFactoryMethodReturnType; - if (preResolved != null) { - return ResolvableType.forClass(preResolved); + ResolvableType returnType = rbd.factoryMethodReturnType; + if (returnType == null) { + Method factoryMethod = rbd.getResolvedFactoryMethod(); + if (factoryMethod != null) { + returnType = ResolvableType.forMethodReturnType(factoryMethod); + } } - else { - Method resolvedFactoryMethod = rbd.getResolvedFactoryMethod(); - if (resolvedFactoryMethod != null) { - if (descriptor.getDependencyType().isAssignableFrom(resolvedFactoryMethod.getReturnType())) { - // Only use factory method metadata if the return type is actually expressive enough - // for our dependency. Otherwise, the returned instance type may have matched instead - // in case of a singleton instance having been registered with the container already. - return ResolvableType.forMethodReturnType(resolvedFactoryMethod); - } + if (returnType != null) { + Class resolvedClass = returnType.resolve(); + if (resolvedClass != null && descriptor.getDependencyType().isAssignableFrom(resolvedClass)) { + // Only use factory method metadata if the return type is actually expressive enough + // for our dependency. Otherwise, the returned instance type may have matched instead + // in case of a singleton instance having been registered with the container already. + return returnType; } - return null; } + return null; } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java index 237e672a3ef..6f61186a2c1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java @@ -64,7 +64,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition { volatile Class resolvedTargetType; /** Package-visible field for caching the return type of a generically typed factory method */ - volatile Class resolvedFactoryMethodReturnType; + volatile ResolvableType factoryMethodReturnType; /** Common lock for the four constructor fields below */ final Object constructorArgumentLock = new Object(); diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java index 32866846dee..d1aa847fe0c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java @@ -248,7 +248,13 @@ public class StaticListableBeanFactory implements ListableBeanFactory { @Override public String[] getBeanNamesForType(ResolvableType type) { - boolean isFactoryType = (type != null && FactoryBean.class.isAssignableFrom(type.getRawClass())); + boolean isFactoryType = false; + if (type != null) { + Class resolved = type.resolve(); + if (resolved != null && FactoryBean.class.isAssignableFrom(resolved)) { + isFactoryType = true; + } + } List matches = new ArrayList(); for (Map.Entry entry : this.beans.entrySet()) { String name = entry.getKey(); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java index ec83ce05899..9efc993097b 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java @@ -1295,7 +1295,6 @@ public class AutowiredAnnotationBeanPostProcessorTests { } catch (UnsatisfiedDependencyException ex) { // expected - ex.printStackTrace(); assertSame(CustomAnnotationRequiredFieldResourceInjectionBean.class, ex.getInjectionPoint().getField().getDeclaringClass()); } @@ -1681,9 +1680,6 @@ public class AutowiredAnnotationBeanPostProcessorTests { assertSame(ir, bean.integerRepositoryMap.get("integerRepository")); } - @Qualifier("integerRepo") - private Repository integerRepositoryQualifierProvider; - @Test public void testGenericsBasedFieldInjectionWithSimpleMatch() { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); @@ -2184,6 +2180,19 @@ public class AutowiredAnnotationBeanPostProcessorTests { assertNotNull(bf.getBean(ProvidedArgumentBean.class)); } + @Test + public void testAnnotatedDefaultConstructor() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + bf.addBeanPostProcessor(new AutowiredAnnotationBeanPostProcessor()); + bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(AnnotatedDefaultConstructorBean.class)); + + assertNotNull(bf.getBean("annotatedBean")); + } + + + @Qualifier("integerRepo") + private Repository integerRepositoryQualifierProvider; + public static class ResourceInjectionBean { @@ -3411,7 +3420,14 @@ public class AutowiredAnnotationBeanPostProcessorTests { tbs.add(new TestBean("tb2")); return tbs; } + } + + public static class AnnotatedDefaultConstructorBean { + + @Autowired + public AnnotatedDefaultConstructorBean() { + } } } diff --git a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java index 48de2bffe97..cff344ddf6c 100644 --- a/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java +++ b/spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java @@ -343,17 +343,15 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe ResolvableType payloadType = null; if (event instanceof PayloadApplicationEvent) { PayloadApplicationEvent payloadEvent = (PayloadApplicationEvent) event; - payloadType = payloadEvent.getResolvableType().as( - PayloadApplicationEvent.class).getGeneric(0); + payloadType = payloadEvent.getResolvableType().as(PayloadApplicationEvent.class).getGeneric(); } for (ResolvableType declaredEventType : this.declaredEventTypes) { - if (!ApplicationEvent.class.isAssignableFrom(declaredEventType.getRawClass()) - && payloadType != null) { + if (!ApplicationEvent.class.isAssignableFrom(declaredEventType.getRawClass()) && payloadType != null) { if (declaredEventType.isAssignableFrom(payloadType)) { return declaredEventType; } } - if (declaredEventType.getRawClass().isAssignableFrom(event.getClass())) { + if (declaredEventType.getRawClass().isInstance(event)) { return declaredEventType; } } diff --git a/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java b/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java index 965fc472333..7f27f1c6025 100644 --- a/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java +++ b/spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -60,8 +60,8 @@ public class GenericApplicationListenerAdapter implements GenericApplicationList @SuppressWarnings("unchecked") public boolean supportsEventType(ResolvableType eventType) { if (this.delegate instanceof SmartApplicationListener) { - Class eventClass = (Class) eventType.getRawClass(); - return ((SmartApplicationListener) this.delegate).supportsEventType(eventClass); + Class eventClass = (Class) eventType.resolve(); + return (eventClass != null && ((SmartApplicationListener) this.delegate).supportsEventType(eventClass)); } else { return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType)); @@ -70,7 +70,7 @@ public class GenericApplicationListenerAdapter implements GenericApplicationList @Override public boolean supportsEventType(Class eventType) { - return supportsEventType(ResolvableType.forType(eventType)); + return supportsEventType(ResolvableType.forClass(eventType)); } @Override diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java index 7e44baba65c..6b6d52742ea 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java @@ -20,6 +20,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.List; import javax.annotation.PostConstruct; import org.junit.Before; @@ -42,6 +43,7 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.componentscan.simple.SimpleComponent; +import org.springframework.core.ResolvableType; import org.springframework.core.annotation.Order; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.DescriptiveResource; @@ -49,6 +51,7 @@ import org.springframework.stereotype.Component; import org.springframework.tests.sample.beans.ITestBean; import org.springframework.tests.sample.beans.TestBean; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import static org.junit.Assert.*; @@ -521,6 +524,33 @@ public class ConfigurationClassPostProcessorTests { assertSame(beanFactory.getBean("genericRepo"), beanFactory.getBean("repoConsumer")); } + @Test + public void genericsBasedInjectionWithLateGenericsMatching() { + beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RepositoryConfiguration.class)); + new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + beanFactory.preInstantiateSingletons(); + + String[] beanNames = beanFactory.getBeanNamesForType(Repository.class); + assertTrue(ObjectUtils.containsElement(beanNames, "stringRepo")); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + } + + @Test + public void genericsBasedInjectionWithEarlyGenericsMatching() { + beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RepositoryConfiguration.class)); + new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory); + + String[] beanNames = beanFactory.getBeanNamesForType(Repository.class); + assertTrue(ObjectUtils.containsElement(beanNames, "stringRepo")); + + beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class)); + assertEquals(1, beanNames.length); + assertEquals("stringRepo", beanNames[0]); + } + @Test public void testSelfReferenceExclusionForFactoryMethodOnSameBean() { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); @@ -615,6 +645,15 @@ public class ConfigurationClassPostProcessorTests { assertSame(ctx.getBean(BarImpl.class), ctx.getBean(FooImpl.class).bar); } + @Test + public void testCollectionInjectionFromSameConfigurationClass() { + ApplicationContext ctx = new AnnotationConfigApplicationContext(CollectionInjectionConfiguration.class); + CollectionInjectionConfiguration bean = ctx.getBean(CollectionInjectionConfiguration.class); + assertNotNull(bean.testBeans); + assertEquals(1, bean.testBeans.size()); + assertSame(ctx.getBean(TestBean.class), bean.testBeans.get(0)); + } + // ------------------------------------------------------------------------- @@ -1253,4 +1292,16 @@ public class ConfigurationClassPostProcessorTests { } } + @Configuration + static class CollectionInjectionConfiguration { + + @Autowired(required = false) + public List testBeans; + + @Bean + public TestBean thing() { + return new TestBean(); + } + } + } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java index 584fa91a2d5..779a36c3a3c 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java @@ -283,7 +283,7 @@ public class InvocableHandlerMethod extends HandlerMethod { return this.returnValue.getClass(); } if (!ResolvableType.NONE.equals(this.returnType)) { - return this.returnType.getRawClass(); + return this.returnType.resolve(); } return super.getParameterType(); } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/DefaultStompSession.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/DefaultStompSession.java index 58f80396a67..aab003e1019 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/DefaultStompSession.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/DefaultStompSession.java @@ -436,7 +436,7 @@ public class DefaultStompSession implements ConnectionHandlingStompSession { return; } Type type = handler.getPayloadType(stompHeaders); - Class payloadType = ResolvableType.forType(type).getRawClass(); + Class payloadType = ResolvableType.forType(type).resolve(); Object object = getMessageConverter().fromMessage(message, payloadType); if (object == null) { throw new MessageConversionException("No suitable converter, payloadType=" + payloadType + diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java index 295795fa137..541eae26904 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java @@ -285,7 +285,7 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { return this.returnValue.getClass(); } if (!ResolvableType.NONE.equals(this.returnType)) { - return this.returnType.getRawClass(); + return this.returnType.resolve(); } return super.getParameterType(); }