Browse Source

ResolvableType-based matching respects generic factory method return type

Includes consistent use of ResolvableType.resolve() wherever applicable.

Issue: SPR-15011
(cherry picked from commit 4c005e6)
pull/1290/head
Juergen Hoeller 9 years ago
parent
commit
b9c4f1fa95
  1. 2
      spring-beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java
  2. 32
      spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java
  3. 16
      spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java
  4. 12
      spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java
  5. 27
      spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java
  6. 2
      spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java
  7. 8
      spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java
  8. 24
      spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java
  9. 8
      spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java
  10. 8
      spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java
  11. 51
      spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java
  12. 2
      spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java
  13. 2
      spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/DefaultStompSession.java
  14. 2
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java

2
spring-beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java

@ -127,7 +127,7 @@ public class NoSuchBeanDefinitionException extends BeansException { @@ -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);
}
/**

32
spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java

@ -74,6 +74,7 @@ import org.springframework.core.GenericTypeResolver; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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;
}
/**

16
spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java

@ -517,7 +517,10 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp @@ -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 @@ -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 @@ -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;
}

12
spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java

@ -1,5 +1,5 @@ @@ -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 { @@ -162,8 +162,8 @@ abstract class AutowireUtils {
* Determine the target type for the generic return type of the given
* <em>generic factory method</em>, where formal type variables are declared
* on the given method itself.
* <p>For example, given a factory method with the following signature,
* if {@code resolveReturnTypeForFactoryMethod()} is invoked with the reflected
* <p>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 { @@ -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) {

27
spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java

@ -147,22 +147,23 @@ public class GenericTypeAwareAutowireCandidateResolver implements AutowireCandid @@ -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;
}

2
spring-beans/src/main/java/org/springframework/beans/factory/support/RootBeanDefinition.java

@ -64,7 +64,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition { @@ -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();

8
spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java

@ -248,7 +248,13 @@ public class StaticListableBeanFactory implements ListableBeanFactory { @@ -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<String> matches = new ArrayList<String>();
for (Map.Entry<String, Object> entry : this.beans.entrySet()) {
String name = entry.getKey();

24
spring-beans/src/test/java/org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessorTests.java

@ -1295,7 +1295,6 @@ public class AutowiredAnnotationBeanPostProcessorTests { @@ -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 { @@ -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 { @@ -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 { @@ -3411,7 +3420,14 @@ public class AutowiredAnnotationBeanPostProcessorTests {
tbs.add(new TestBean("tb2"));
return tbs;
}
}
public static class AnnotatedDefaultConstructorBean {
@Autowired
public AnnotatedDefaultConstructorBean() {
}
}
}

8
spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java

@ -343,17 +343,15 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe @@ -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;
}
}

8
spring-context/src/main/java/org/springframework/context/event/GenericApplicationListenerAdapter.java

@ -1,5 +1,5 @@ @@ -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 @@ -60,8 +60,8 @@ public class GenericApplicationListenerAdapter implements GenericApplicationList
@SuppressWarnings("unchecked")
public boolean supportsEventType(ResolvableType eventType) {
if (this.delegate instanceof SmartApplicationListener) {
Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.getRawClass();
return ((SmartApplicationListener) this.delegate).supportsEventType(eventClass);
Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) 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 @@ -70,7 +70,7 @@ public class GenericApplicationListenerAdapter implements GenericApplicationList
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return supportsEventType(ResolvableType.forType(eventType));
return supportsEventType(ResolvableType.forClass(eventType));
}
@Override

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

@ -20,6 +20,7 @@ import java.lang.annotation.ElementType; @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -1253,4 +1292,16 @@ public class ConfigurationClassPostProcessorTests {
}
}
@Configuration
static class CollectionInjectionConfiguration {
@Autowired(required = false)
public List<TestBean> testBeans;
@Bean
public TestBean thing() {
return new TestBean();
}
}
}

2
spring-messaging/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java

@ -283,7 +283,7 @@ public class InvocableHandlerMethod extends HandlerMethod { @@ -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();
}

2
spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/DefaultStompSession.java

@ -436,7 +436,7 @@ public class DefaultStompSession implements ConnectionHandlingStompSession { @@ -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 +

2
spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java

@ -285,7 +285,7 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { @@ -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();
}

Loading…
Cancel
Save