ResolvableType-based matching respects generic factory method return type
Includes consistent use of ResolvableType.resolve() wherever applicable. Issue: SPR-15011
This commit is contained in:
parent
e9b4cac47f
commit
4c005e6336
|
@ -109,7 +109,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
@ -672,9 +673,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;
|
||||
|
@ -698,11 +699,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) {
|
||||
|
@ -736,8 +738,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) {
|
||||
|
@ -747,22 +753,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -513,7 +513,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});
|
||||
|
||||
|
@ -553,6 +556,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);
|
||||
}
|
||||
}
|
||||
|
@ -1443,6 +1453,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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
* <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 {
|
|||
* @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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
ResolvableType returnType = rbd.factoryMethodReturnType;
|
||||
if (returnType == null) {
|
||||
Method factoryMethod = rbd.getResolvedFactoryMethod();
|
||||
if (factoryMethod != null) {
|
||||
returnType = ResolvableType.forMethodReturnType(factoryMethod);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -65,7 +65,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();
|
||||
|
|
|
@ -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<>();
|
||||
for (Map.Entry<String, Object> entry : this.beans.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
|
|
|
@ -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();
|
||||
|
@ -2194,6 +2190,10 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
|||
}
|
||||
|
||||
|
||||
@Qualifier("integerRepo")
|
||||
private Repository<?> integerRepositoryQualifierProvider;
|
||||
|
||||
|
||||
public static class ResourceInjectionBean {
|
||||
|
||||
@Autowired(required = false)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<? 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
|
|||
|
||||
@Override
|
||||
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
|
||||
return supportsEventType(ResolvableType.forType(eventType));
|
||||
return supportsEventType(ResolvableType.forClass(eventType));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -43,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;
|
||||
|
@ -50,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.*;
|
||||
|
||||
|
@ -522,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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 +
|
||||
|
|
|
@ -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…
Reference in New Issue