Skip to content

Commit b913578

Browse files
committed
Fixed type prediction for generic factory methods
We're consistently resolving class names now, and the entire algorithm moved from GenericTypeResolver to the internal AutowireUtils helper in the bean factory package. Issue: SPR-10411 (cherry picked from commit 8b1927f)
1 parent b62652a commit b913578

File tree

5 files changed

+312
-69
lines changed

5 files changed

+312
-69
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,8 @@ protected Class<?> getTypeForFactoryMethod(String beanName, RootBeanDefinition m
647647
if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic &&
648648
factoryMethod.getName().equals(mbd.getFactoryMethodName()) &&
649649
factoryMethod.getParameterTypes().length >= minNrOfArgs) {
650-
Class<?> returnType = GenericTypeResolver.resolveReturnTypeForGenericMethod(factoryMethod, args);
650+
Class<?> returnType = AutowireUtils.resolveReturnTypeForFactoryMethod(
651+
factoryMethod, args, getBeanClassLoader());
651652
if (returnType != null) {
652653
returnTypes.add(returnType);
653654
}

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

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2013 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.
@@ -23,20 +23,25 @@
2323
import java.lang.reflect.InvocationTargetException;
2424
import java.lang.reflect.Method;
2525
import java.lang.reflect.Modifier;
26+
import java.lang.reflect.ParameterizedType;
2627
import java.lang.reflect.Proxy;
28+
import java.lang.reflect.Type;
29+
import java.lang.reflect.TypeVariable;
2730
import java.util.Arrays;
2831
import java.util.Comparator;
2932
import java.util.Set;
3033

3134
import org.springframework.beans.factory.ObjectFactory;
35+
import org.springframework.util.Assert;
3236
import org.springframework.util.ClassUtils;
3337

3438
/**
35-
* Utility class that contains various methods useful for
36-
* the implementation of autowire-capable bean factories.
39+
* Utility class that contains various methods useful for the implementation of
40+
* autowire-capable bean factories.
3741
*
3842
* @author Juergen Hoeller
3943
* @author Mark Fisher
44+
* @author Sam Brannen
4045
* @since 1.1.2
4146
* @see AbstractAutowireCapableBeanFactory
4247
*/
@@ -117,8 +122,8 @@ public static boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) {
117122
public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set<Class> interfaces) {
118123
Method setter = pd.getWriteMethod();
119124
if (setter != null) {
120-
Class targetClass = setter.getDeclaringClass();
121-
for (Class ifc : interfaces) {
125+
Class<?> targetClass = setter.getDeclaringClass();
126+
for (Class<?> ifc : interfaces) {
122127
if (ifc.isAssignableFrom(targetClass) &&
123128
ClassUtils.hasMethod(ifc, setter.getName(), setter.getParameterTypes())) {
124129
return true;
@@ -135,7 +140,7 @@ public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set<Clas
135140
* @param requiredType the type to assign the result to
136141
* @return the resolved value
137142
*/
138-
public static Object resolveAutowiringValue(Object autowiringValue, Class requiredType) {
143+
public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
139144
if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
140145
ObjectFactory factory = (ObjectFactory) autowiringValue;
141146
if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
@@ -149,6 +154,106 @@ public static Object resolveAutowiringValue(Object autowiringValue, Class requir
149154
return autowiringValue;
150155
}
151156

157+
/**
158+
* Determine the target type for the generic return type of the given
159+
* <em>generic factory method</em>, where formal type variables are declared
160+
* on the given method itself.
161+
* <p>For example, given a factory method with the following signature,
162+
* if {@code resolveReturnTypeForFactoryMethod()} is invoked with the reflected
163+
* method for {@code creatProxy()} and an {@code Object[]} array containing
164+
* {@code MyService.class}, {@code resolveReturnTypeForFactoryMethod()} will
165+
* infer that the target return type is {@code MyService}.
166+
* <pre class="code">{@code public static <T> T createProxy(Class<T> clazz)}</pre>
167+
* <h4>Possible Return Values</h4>
168+
* <ul>
169+
* <li>the target return type, if it can be inferred</li>
170+
* <li>the {@linkplain Method#getReturnType() standard return type}, if
171+
* the given {@code method} does not declare any {@linkplain
172+
* Method#getTypeParameters() formal type variables}</li>
173+
* <li>the {@linkplain Method#getReturnType() standard return type}, if the
174+
* target return type cannot be inferred (e.g., due to type erasure)</li>
175+
* <li>{@code null}, if the length of the given arguments array is shorter
176+
* than the length of the {@linkplain
177+
* Method#getGenericParameterTypes() formal argument list} for the given
178+
* method</li>
179+
* </ul>
180+
* @param method the method to introspect (never {@code null})
181+
* @param args the arguments that will be supplied to the method when it is
182+
* invoked (never {@code null})
183+
* @param classLoader the ClassLoader to resolve class names against, if necessary
184+
* (never {@code null})
185+
* @return the resolved target return type, the standard return type, or {@code null}
186+
* @since 3.2.5
187+
*/
188+
public static Class<?> resolveReturnTypeForFactoryMethod(Method method, Object[] args, ClassLoader classLoader) {
189+
Assert.notNull(method, "Method must not be null");
190+
Assert.notNull(args, "Argument array must not be null");
191+
Assert.notNull(classLoader, "ClassLoader must not be null");
192+
193+
TypeVariable<Method>[] declaredTypeVariables = method.getTypeParameters();
194+
Type genericReturnType = method.getGenericReturnType();
195+
Type[] methodArgumentTypes = method.getGenericParameterTypes();
196+
197+
// No declared type variables to inspect, so just return the standard return type.
198+
if (declaredTypeVariables.length == 0) {
199+
return method.getReturnType();
200+
}
201+
202+
// The supplied argument list is too short for the method's signature, so
203+
// return null, since such a method invocation would fail.
204+
if (args.length < methodArgumentTypes.length) {
205+
return null;
206+
}
207+
208+
// Ensure that the type variable (e.g., T) is declared directly on the method
209+
// itself (e.g., via <T>), not on the enclosing class or interface.
210+
boolean locallyDeclaredTypeVariableMatchesReturnType = false;
211+
for (TypeVariable<Method> currentTypeVariable : declaredTypeVariables) {
212+
if (currentTypeVariable.equals(genericReturnType)) {
213+
locallyDeclaredTypeVariableMatchesReturnType = true;
214+
break;
215+
}
216+
}
217+
218+
if (locallyDeclaredTypeVariableMatchesReturnType) {
219+
for (int i = 0; i < methodArgumentTypes.length; i++) {
220+
Type currentMethodArgumentType = methodArgumentTypes[i];
221+
if (currentMethodArgumentType.equals(genericReturnType)) {
222+
return args[i].getClass();
223+
}
224+
if (currentMethodArgumentType instanceof ParameterizedType) {
225+
ParameterizedType parameterizedType = (ParameterizedType) currentMethodArgumentType;
226+
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
227+
for (Type typeArg : actualTypeArguments) {
228+
if (typeArg.equals(genericReturnType)) {
229+
Object arg = args[i];
230+
if (arg instanceof Class) {
231+
return (Class<?>) arg;
232+
}
233+
else if (arg instanceof String) {
234+
try {
235+
return classLoader.loadClass((String) arg);
236+
}
237+
catch (ClassNotFoundException ex) {
238+
throw new IllegalStateException(
239+
"Could not resolve specified class name argument [" + arg + "]", ex);
240+
}
241+
}
242+
else {
243+
// Consider adding logic to determine the class of the typeArg, if possible.
244+
// For now, just fall back...
245+
return method.getReturnType();
246+
}
247+
}
248+
}
249+
}
250+
}
251+
}
252+
253+
// Fall back...
254+
return method.getReturnType();
255+
}
256+
152257

153258
/**
154259
* Reflective InvocationHandler for lazy access to the current target object.
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.beans.factory.support;
18+
19+
import java.lang.reflect.Method;
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
23+
import org.junit.Test;
24+
25+
import org.springframework.util.ReflectionUtils;
26+
27+
import static org.junit.Assert.*;
28+
29+
/**
30+
* @author Juergen Hoeller
31+
* @author Sam Brannen
32+
*/
33+
public class AutowireUtilsTests {
34+
35+
@Test
36+
public void genericMethodReturnTypes() {
37+
Method notParameterized = ReflectionUtils.findMethod(MyTypeWithMethods.class, "notParameterized", new Class[]{});
38+
assertEquals(String.class,
39+
AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterized, new Object[]{}, getClass().getClassLoader()));
40+
41+
Method notParameterizedWithArguments = ReflectionUtils.findMethod(MyTypeWithMethods.class, "notParameterizedWithArguments",
42+
new Class[] { Integer.class, Boolean.class });
43+
assertEquals(String.class,
44+
AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterizedWithArguments, new Object[] { 99, true }, getClass().getClassLoader()));
45+
46+
Method createProxy = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createProxy", new Class[] { Object.class });
47+
assertEquals(String.class,
48+
AutowireUtils.resolveReturnTypeForFactoryMethod(createProxy, new Object[] { "foo" }, getClass().getClassLoader()));
49+
50+
Method createNamedProxyWithDifferentTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy",
51+
new Class[] { String.class, Object.class });
52+
// one argument to few
53+
assertNull(
54+
AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDifferentTypes, new Object[]{"enigma"}, getClass().getClassLoader()));
55+
assertEquals(Long.class,
56+
AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L }, getClass().getClassLoader()));
57+
58+
Method createNamedProxyWithDuplicateTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy",
59+
new Class[] { String.class, Object.class });
60+
assertEquals(String.class,
61+
AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDuplicateTypes, new Object[] { "enigma", "foo" }, getClass().getClassLoader()));
62+
63+
Method createMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createMock", new Class[] { Class.class });
64+
assertEquals(Runnable.class,
65+
AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[] { Runnable.class }, getClass().getClassLoader()));
66+
assertEquals(Runnable.class,
67+
AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[] { Runnable.class.getName() }, getClass().getClassLoader()));
68+
69+
Method createNamedMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedMock", new Class[] { String.class,
70+
Class.class });
71+
assertEquals(Runnable.class,
72+
AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedMock, new Object[] { "foo", Runnable.class }, getClass().getClassLoader()));
73+
74+
Method createVMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createVMock",
75+
new Class[] { Object.class, Class.class });
76+
assertEquals(Runnable.class,
77+
AutowireUtils.resolveReturnTypeForFactoryMethod(createVMock, new Object[] { "foo", Runnable.class }, getClass().getClassLoader()));
78+
79+
// Ideally we would expect String.class instead of Object.class, but
80+
// resolveReturnTypeForFactoryMethod() does not currently support this form of
81+
// look-up.
82+
Method extractValueFrom = ReflectionUtils.findMethod(MyTypeWithMethods.class, "extractValueFrom",
83+
new Class[] { MyInterfaceType.class });
84+
assertEquals(Object.class,
85+
AutowireUtils.resolveReturnTypeForFactoryMethod(extractValueFrom, new Object[] { new MySimpleInterfaceType() }, getClass().getClassLoader()));
86+
87+
// Ideally we would expect Boolean.class instead of Object.class, but this
88+
// information is not available at run-time due to type erasure.
89+
Map<Integer, Boolean> map = new HashMap<Integer, Boolean>();
90+
map.put(0, false);
91+
map.put(1, true);
92+
Method extractMagicValue = ReflectionUtils.findMethod(MyTypeWithMethods.class, "extractMagicValue", new Class[] { Map.class });
93+
assertEquals(Object.class, AutowireUtils.resolveReturnTypeForFactoryMethod(extractMagicValue, new Object[] { map }, getClass().getClassLoader()));
94+
}
95+
96+
97+
public interface MyInterfaceType<T> {
98+
}
99+
100+
public class MySimpleInterfaceType implements MyInterfaceType<String> {
101+
}
102+
103+
public static class MyTypeWithMethods<T> {
104+
105+
public MyInterfaceType<Integer> integer() {
106+
return null;
107+
}
108+
109+
public MySimpleInterfaceType string() {
110+
return null;
111+
}
112+
113+
public Object object() {
114+
return null;
115+
}
116+
117+
@SuppressWarnings("rawtypes")
118+
public MyInterfaceType raw() {
119+
return null;
120+
}
121+
122+
public String notParameterized() {
123+
return null;
124+
}
125+
126+
public String notParameterizedWithArguments(Integer x, Boolean b) {
127+
return null;
128+
}
129+
130+
/**
131+
* Simulates a factory method that wraps the supplied object in a proxy of the
132+
* same type.
133+
*/
134+
public static <T> T createProxy(T object) {
135+
return null;
136+
}
137+
138+
/**
139+
* Similar to {@link #createProxy(Object)} but adds an additional argument before
140+
* the argument of type {@code T}. Note that they may potentially be of the same
141+
* time when invoked!
142+
*/
143+
public static <T> T createNamedProxy(String name, T object) {
144+
return null;
145+
}
146+
147+
/**
148+
* Simulates factory methods found in libraries such as Mockito and EasyMock.
149+
*/
150+
public static <MOCK> MOCK createMock(Class<MOCK> toMock) {
151+
return null;
152+
}
153+
154+
/**
155+
* Similar to {@link #createMock(Class)} but adds an additional method argument
156+
* before the parameterized argument.
157+
*/
158+
public static <T> T createNamedMock(String name, Class<T> toMock) {
159+
return null;
160+
}
161+
162+
/**
163+
* Similar to {@link #createNamedMock(String, Class)} but adds an additional
164+
* parameterized type.
165+
*/
166+
public static <V extends Object, T> T createVMock(V name, Class<T> toMock) {
167+
return null;
168+
}
169+
170+
/**
171+
* Extract some value of the type supported by the interface (i.e., by a concrete,
172+
* non-generic implementation of the interface).
173+
*/
174+
public static <T> T extractValueFrom(MyInterfaceType<T> myInterfaceType) {
175+
return null;
176+
}
177+
178+
/**
179+
* Extract some magic value from the supplied map.
180+
*/
181+
public static <K, V> V extractMagicValue(Map<K, V> map) {
182+
return null;
183+
}
184+
185+
public void readIntegerInputMessage(MyInterfaceType<Integer> message) {
186+
}
187+
188+
public void readIntegerArrayInputMessage(MyInterfaceType<Integer>[] message) {
189+
}
190+
191+
public void readGenericArrayInputMessage(T[] message) {
192+
}
193+
}
194+
195+
}

spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,10 @@ public static Class<?> resolveReturnType(Method method, Class<?> clazz) {
131131
* invoked, never {@code null}
132132
* @return the resolved target return type, the standard return type, or {@code null}
133133
* @since 3.2
134-
* @see #resolveReturnType
134+
* @deprecated in favor of resolveReturnTypeForFactoryMethod in the internal
135+
* AutowireUtils class in the beans module; we do not expect other use of it!
135136
*/
137+
@Deprecated
136138
public static Class<?> resolveReturnTypeForGenericMethod(Method method, Object[] args) {
137139
Assert.notNull(method, "Method must not be null");
138140
Assert.notNull(args, "Argument array must not be null");

0 commit comments

Comments
 (0)