Skip to content

Commit 4c005e6

Browse files
committed
ResolvableType-based matching respects generic factory method return type
Includes consistent use of ResolvableType.resolve() wherever applicable. Issue: SPR-15011
1 parent e9b4cac commit 4c005e6

File tree

14 files changed

+106
-52
lines changed

14 files changed

+106
-52
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public String getBeanName() {
109109
* that failed.
110110
*/
111111
public Class<?> getBeanType() {
112-
return (this.resolvableType != null ? this.resolvableType.getRawClass() : null);
112+
return (this.resolvableType != null ? this.resolvableType.resolve() : null);
113113
}
114114

115115
/**

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

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
import org.springframework.core.MethodParameter;
7575
import org.springframework.core.ParameterNameDiscoverer;
7676
import org.springframework.core.PriorityOrdered;
77+
import org.springframework.core.ResolvableType;
7778
import org.springframework.util.ClassUtils;
7879
import org.springframework.util.ObjectUtils;
7980
import org.springframework.util.ReflectionUtils;
@@ -672,9 +673,9 @@ protected Class<?> determineTargetType(String beanName, RootBeanDefinition mbd,
672673
* @see #createBean
673674
*/
674675
protected Class<?> getTypeForFactoryMethod(String beanName, RootBeanDefinition mbd, Class<?>... typesToMatch) {
675-
Class<?> preResolved = mbd.resolvedFactoryMethodReturnType;
676-
if (preResolved != null) {
677-
return preResolved;
676+
ResolvableType cachedReturnType = mbd.factoryMethodReturnType;
677+
if (cachedReturnType != null) {
678+
return cachedReturnType.resolve();
678679
}
679680

680681
Class<?> factoryClass;
@@ -698,11 +699,12 @@ protected Class<?> getTypeForFactoryMethod(String beanName, RootBeanDefinition m
698699
if (factoryClass == null) {
699700
return null;
700701
}
702+
factoryClass = ClassUtils.getUserClass(factoryClass);
701703

702704
// If all factory methods have the same return type, return that type.
703705
// Can't clearly figure out exact method due to type converting / autowiring!
704706
Class<?> commonType = null;
705-
boolean cache = false;
707+
Method uniqueCandidate = null;
706708
int minNrOfArgs = mbd.getConstructorArgumentValues().getArgumentCount();
707709
Method[] candidates = ReflectionUtils.getUniqueDeclaredMethods(factoryClass);
708710
for (Method factoryMethod : candidates) {
@@ -736,8 +738,12 @@ protected Class<?> getTypeForFactoryMethod(String beanName, RootBeanDefinition m
736738
Class<?> returnType = AutowireUtils.resolveReturnTypeForFactoryMethod(
737739
factoryMethod, args, getBeanClassLoader());
738740
if (returnType != null) {
739-
cache = true;
741+
uniqueCandidate = (commonType == null ? factoryMethod : null);
740742
commonType = ClassUtils.determineCommonAncestor(returnType, commonType);
743+
if (commonType == null) {
744+
// Ambiguous return types found: return null to indicate "not determinable".
745+
return null;
746+
}
741747
}
742748
}
743749
catch (Throwable ex) {
@@ -747,22 +753,22 @@ protected Class<?> getTypeForFactoryMethod(String beanName, RootBeanDefinition m
747753
}
748754
}
749755
else {
756+
uniqueCandidate = (commonType == null ? factoryMethod : null);
750757
commonType = ClassUtils.determineCommonAncestor(factoryMethod.getReturnType(), commonType);
758+
if (commonType == null) {
759+
// Ambiguous return types found: return null to indicate "not determinable".
760+
return null;
761+
}
751762
}
752763
}
753764
}
754765

755766
if (commonType != null) {
756767
// Clear return type found: all factory methods return same type.
757-
if (cache) {
758-
mbd.resolvedFactoryMethodReturnType = commonType;
759-
}
760-
return commonType;
761-
}
762-
else {
763-
// Ambiguous return types found: return null to indicate "not determinable".
764-
return null;
768+
mbd.factoryMethodReturnType = (uniqueCandidate != null ?
769+
ResolvableType.forMethodReturnType(uniqueCandidate) : ResolvableType.forClass(commonType));
765770
}
771+
return commonType;
766772
}
767773

768774
/**

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,10 @@ else if (containsSingleton(beanName) && !containsBeanDefinition(beanName)) {
513513
// Retrieve corresponding bean definition.
514514
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
515515

516-
Class<?> classToMatch = typeToMatch.getRawClass();
516+
Class<?> classToMatch = typeToMatch.resolve();
517+
if (classToMatch == null) {
518+
classToMatch = FactoryBean.class;
519+
}
517520
Class<?>[] typesToMatch = (FactoryBean.class == classToMatch ?
518521
new Class<?>[] {classToMatch} : new Class<?>[] {FactoryBean.class, classToMatch});
519522

@@ -553,6 +556,13 @@ else if (BeanFactoryUtils.isFactoryDereference(name)) {
553556
}
554557
}
555558

559+
ResolvableType resolvableType = mbd.targetType;
560+
if (resolvableType == null) {
561+
resolvableType = mbd.factoryMethodReturnType;
562+
}
563+
if (resolvableType != null && resolvableType.resolve() == beanType) {
564+
return typeToMatch.isAssignableFrom(resolvableType);
565+
}
556566
return typeToMatch.isAssignableFrom(beanType);
557567
}
558568
}
@@ -1443,6 +1453,10 @@ protected Object evaluateBeanDefinitionString(String value, BeanDefinition beanD
14431453
* @return the type of the bean, or {@code null} if not predictable
14441454
*/
14451455
protected Class<?> predictBeanType(String beanName, RootBeanDefinition mbd, Class<?>... typesToMatch) {
1456+
Class<?> targetType = mbd.getTargetType();
1457+
if (targetType != null) {
1458+
return targetType;
1459+
}
14461460
if (mbd.getFactoryMethodName() != null) {
14471461
return null;
14481462
}

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -162,8 +162,8 @@ public static Object resolveAutowiringValue(Object autowiringValue, Class<?> req
162162
* Determine the target type for the generic return type of the given
163163
* <em>generic factory method</em>, where formal type variables are declared
164164
* on the given method itself.
165-
* <p>For example, given a factory method with the following signature,
166-
* if {@code resolveReturnTypeForFactoryMethod()} is invoked with the reflected
165+
* <p>For example, given a factory method with the following signature, if
166+
* {@code resolveReturnTypeForFactoryMethod()} is invoked with the reflected
167167
* method for {@code creatProxy()} and an {@code Object[]} array containing
168168
* {@code MyService.class}, {@code resolveReturnTypeForFactoryMethod()} will
169169
* infer that the target return type is {@code MyService}.
@@ -184,9 +184,9 @@ public static Object resolveAutowiringValue(Object autowiringValue, Class<?> req
184184
* @param method the method to introspect (never {@code null})
185185
* @param args the arguments that will be supplied to the method when it is
186186
* invoked (never {@code null})
187-
* @param classLoader the ClassLoader to resolve class names against, if necessary
188-
* (never {@code null})
189-
* @return the resolved target return type, the standard return type, or {@code null}
187+
* @param classLoader the ClassLoader to resolve class names against,
188+
* if necessary (never {@code null})
189+
* @return the resolved target return type or the standard method return type
190190
* @since 3.2.5
191191
*/
192192
public static Class<?> resolveReturnTypeForFactoryMethod(Method method, Object[] args, ClassLoader classLoader) {

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

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -147,22 +147,23 @@ protected RootBeanDefinition getResolvedDecoratedDefinition(RootBeanDefinition r
147147
protected ResolvableType getReturnTypeForFactoryMethod(RootBeanDefinition rbd, DependencyDescriptor descriptor) {
148148
// Should typically be set for any kind of factory method, since the BeanFactory
149149
// pre-resolves them before reaching out to the AutowireCandidateResolver...
150-
Class<?> preResolved = rbd.resolvedFactoryMethodReturnType;
151-
if (preResolved != null) {
152-
return ResolvableType.forClass(preResolved);
150+
ResolvableType returnType = rbd.factoryMethodReturnType;
151+
if (returnType == null) {
152+
Method factoryMethod = rbd.getResolvedFactoryMethod();
153+
if (factoryMethod != null) {
154+
returnType = ResolvableType.forMethodReturnType(factoryMethod);
155+
}
153156
}
154-
else {
155-
Method resolvedFactoryMethod = rbd.getResolvedFactoryMethod();
156-
if (resolvedFactoryMethod != null) {
157-
if (descriptor.getDependencyType().isAssignableFrom(resolvedFactoryMethod.getReturnType())) {
158-
// Only use factory method metadata if the return type is actually expressive enough
159-
// for our dependency. Otherwise, the returned instance type may have matched instead
160-
// in case of a singleton instance having been registered with the container already.
161-
return ResolvableType.forMethodReturnType(resolvedFactoryMethod);
162-
}
157+
if (returnType != null) {
158+
Class<?> resolvedClass = returnType.resolve();
159+
if (resolvedClass != null && descriptor.getDependencyType().isAssignableFrom(resolvedClass)) {
160+
// Only use factory method metadata if the return type is actually expressive enough
161+
// for our dependency. Otherwise, the returned instance type may have matched instead
162+
// in case of a singleton instance having been registered with the container already.
163+
return returnType;
163164
}
164-
return null;
165165
}
166+
return null;
166167
}
167168

168169

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition {
6565
volatile Class<?> resolvedTargetType;
6666

6767
/** Package-visible field for caching the return type of a generically typed factory method */
68-
volatile Class<?> resolvedFactoryMethodReturnType;
68+
volatile ResolvableType factoryMethodReturnType;
6969

7070
/** Common lock for the four constructor fields below */
7171
final Object constructorArgumentLock = new Object();

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,13 @@ public String[] getBeanDefinitionNames() {
248248

249249
@Override
250250
public String[] getBeanNamesForType(ResolvableType type) {
251-
boolean isFactoryType = (type != null && FactoryBean.class.isAssignableFrom(type.getRawClass()));
251+
boolean isFactoryType = false;
252+
if (type != null) {
253+
Class<?> resolved = type.resolve();
254+
if (resolved != null && FactoryBean.class.isAssignableFrom(resolved)) {
255+
isFactoryType = true;
256+
}
257+
}
252258
List<String> matches = new ArrayList<>();
253259
for (Map.Entry<String, Object> entry : this.beans.entrySet()) {
254260
String name = entry.getKey();

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,7 +1295,6 @@ public void testCustomAnnotationRequiredFieldResourceInjectionFailsWhenMultipleD
12951295
}
12961296
catch (UnsatisfiedDependencyException ex) {
12971297
// expected
1298-
ex.printStackTrace();
12991298
assertSame(CustomAnnotationRequiredFieldResourceInjectionBean.class,
13001299
ex.getInjectionPoint().getField().getDeclaringClass());
13011300
}
@@ -1681,9 +1680,6 @@ public void testGenericsBasedFieldInjectionWithMocks() {
16811680
assertSame(ir, bean.integerRepositoryMap.get("integerRepository"));
16821681
}
16831682

1684-
@Qualifier("integerRepo")
1685-
private Repository<?> integerRepositoryQualifierProvider;
1686-
16871683
@Test
16881684
public void testGenericsBasedFieldInjectionWithSimpleMatch() {
16891685
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
@@ -2194,6 +2190,10 @@ public void testAnnotatedDefaultConstructor() {
21942190
}
21952191

21962192

2193+
@Qualifier("integerRepo")
2194+
private Repository<?> integerRepositoryQualifierProvider;
2195+
2196+
21972197
public static class ResourceInjectionBean {
21982198

21992199
@Autowired(required = false)

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -343,17 +343,15 @@ private ResolvableType getResolvableType(ApplicationEvent event) {
343343
ResolvableType payloadType = null;
344344
if (event instanceof PayloadApplicationEvent) {
345345
PayloadApplicationEvent<?> payloadEvent = (PayloadApplicationEvent<?>) event;
346-
payloadType = payloadEvent.getResolvableType().as(
347-
PayloadApplicationEvent.class).getGeneric(0);
346+
payloadType = payloadEvent.getResolvableType().as(PayloadApplicationEvent.class).getGeneric();
348347
}
349348
for (ResolvableType declaredEventType : this.declaredEventTypes) {
350-
if (!ApplicationEvent.class.isAssignableFrom(declaredEventType.getRawClass())
351-
&& payloadType != null) {
349+
if (!ApplicationEvent.class.isAssignableFrom(declaredEventType.getRawClass()) && payloadType != null) {
352350
if (declaredEventType.isAssignableFrom(payloadType)) {
353351
return declaredEventType;
354352
}
355353
}
356-
if (declaredEventType.getRawClass().isAssignableFrom(event.getClass())) {
354+
if (declaredEventType.getRawClass().isInstance(event)) {
357355
return declaredEventType;
358356
}
359357
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -60,8 +60,8 @@ public void onApplicationEvent(ApplicationEvent event) {
6060
@SuppressWarnings("unchecked")
6161
public boolean supportsEventType(ResolvableType eventType) {
6262
if (this.delegate instanceof SmartApplicationListener) {
63-
Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.getRawClass();
64-
return ((SmartApplicationListener) this.delegate).supportsEventType(eventClass);
63+
Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.resolve();
64+
return (eventClass != null && ((SmartApplicationListener) this.delegate).supportsEventType(eventClass));
6565
}
6666
else {
6767
return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType));
@@ -70,7 +70,7 @@ public boolean supportsEventType(ResolvableType eventType) {
7070

7171
@Override
7272
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
73-
return supportsEventType(ResolvableType.forType(eventType));
73+
return supportsEventType(ResolvableType.forClass(eventType));
7474
}
7575

7676
@Override

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,15 @@
4343
import org.springframework.beans.factory.support.RootBeanDefinition;
4444
import org.springframework.context.ApplicationContext;
4545
import org.springframework.context.annotation.componentscan.simple.SimpleComponent;
46+
import org.springframework.core.ResolvableType;
4647
import org.springframework.core.annotation.Order;
4748
import org.springframework.core.env.StandardEnvironment;
4849
import org.springframework.core.io.DescriptiveResource;
4950
import org.springframework.stereotype.Component;
5051
import org.springframework.tests.sample.beans.ITestBean;
5152
import org.springframework.tests.sample.beans.TestBean;
5253
import org.springframework.util.Assert;
54+
import org.springframework.util.ObjectUtils;
5355

5456
import static org.junit.Assert.*;
5557

@@ -522,6 +524,33 @@ public void genericsBasedInjectionWithWildcardWithGenericExtendsMatch() {
522524
assertSame(beanFactory.getBean("genericRepo"), beanFactory.getBean("repoConsumer"));
523525
}
524526

527+
@Test
528+
public void genericsBasedInjectionWithLateGenericsMatching() {
529+
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RepositoryConfiguration.class));
530+
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
531+
beanFactory.preInstantiateSingletons();
532+
533+
String[] beanNames = beanFactory.getBeanNamesForType(Repository.class);
534+
assertTrue(ObjectUtils.containsElement(beanNames, "stringRepo"));
535+
536+
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class));
537+
assertEquals(1, beanNames.length);
538+
assertEquals("stringRepo", beanNames[0]);
539+
}
540+
541+
@Test
542+
public void genericsBasedInjectionWithEarlyGenericsMatching() {
543+
beanFactory.registerBeanDefinition("configClass", new RootBeanDefinition(RepositoryConfiguration.class));
544+
new ConfigurationClassPostProcessor().postProcessBeanFactory(beanFactory);
545+
546+
String[] beanNames = beanFactory.getBeanNamesForType(Repository.class);
547+
assertTrue(ObjectUtils.containsElement(beanNames, "stringRepo"));
548+
549+
beanNames = beanFactory.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, String.class));
550+
assertEquals(1, beanNames.length);
551+
assertEquals("stringRepo", beanNames[0]);
552+
}
553+
525554
@Test
526555
public void testSelfReferenceExclusionForFactoryMethodOnSameBean() {
527556
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ public Class<?> getParameterType() {
283283
return this.returnValue.getClass();
284284
}
285285
if (!ResolvableType.NONE.equals(this.returnType)) {
286-
return this.returnType.getRawClass();
286+
return this.returnType.resolve();
287287
}
288288
return super.getParameterType();
289289
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ private void invokeHandler(StompFrameHandler handler, Message<byte[]> message, S
436436
return;
437437
}
438438
Type type = handler.getPayloadType(stompHeaders);
439-
Class<?> payloadType = ResolvableType.forType(type).getRawClass();
439+
Class<?> payloadType = ResolvableType.forType(type).resolve();
440440
Object object = getMessageConverter().fromMessage(message, payloadType);
441441
if (object == null) {
442442
throw new MessageConversionException("No suitable converter, payloadType=" + payloadType +

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ public Class<?> getParameterType() {
285285
return this.returnValue.getClass();
286286
}
287287
if (!ResolvableType.NONE.equals(this.returnType)) {
288-
return this.returnType.getRawClass();
288+
return this.returnType.resolve();
289289
}
290290
return super.getParameterType();
291291
}

0 commit comments

Comments
 (0)