Skip to content

Commit 8b1927f

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
1 parent 41f041e commit 8b1927f

File tree

4 files changed

+305
-66
lines changed

4 files changed

+305
-66
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
@@ -665,7 +665,8 @@ protected Class<?> getTypeForFactoryMethod(String beanName, RootBeanDefinition m
665665
if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic &&
666666
factoryMethod.getName().equals(mbd.getFactoryMethodName()) &&
667667
factoryMethod.getParameterTypes().length >= minNrOfArgs) {
668-
Class<?> returnType = GenericTypeResolver.resolveReturnTypeForGenericMethod(factoryMethod, args);
668+
Class<?> returnType = AutowireUtils.resolveReturnTypeForFactoryMethod(
669+
factoryMethod, args, getBeanClassLoader());
669670
if (returnType != null) {
670671
returnTypes.add(returnType);
671672
}

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

Lines changed: 107 additions & 4 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,12 +23,16 @@
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
/**
@@ -37,6 +41,7 @@
3741
*
3842
* @author Juergen Hoeller
3943
* @author Mark Fisher
44+
* @author Sam Brannen
4045
* @since 1.1.2
4146
* @see AbstractAutowireCapableBeanFactory
4247
*/
@@ -119,8 +124,8 @@ public static boolean isExcludedFromDependencyCheck(PropertyDescriptor pd) {
119124
public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set<Class> interfaces) {
120125
Method setter = pd.getWriteMethod();
121126
if (setter != null) {
122-
Class targetClass = setter.getDeclaringClass();
123-
for (Class ifc : interfaces) {
127+
Class<?> targetClass = setter.getDeclaringClass();
128+
for (Class<?> ifc : interfaces) {
124129
if (ifc.isAssignableFrom(targetClass) &&
125130
ClassUtils.hasMethod(ifc, setter.getName(), setter.getParameterTypes())) {
126131
return true;
@@ -137,7 +142,7 @@ public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set<Clas
137142
* @param requiredType the type to assign the result to
138143
* @return the resolved value
139144
*/
140-
public static Object resolveAutowiringValue(Object autowiringValue, Class requiredType) {
145+
public static Object resolveAutowiringValue(Object autowiringValue, Class<?> requiredType) {
141146
if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) {
142147
ObjectFactory factory = (ObjectFactory) autowiringValue;
143148
if (autowiringValue instanceof Serializable && requiredType.isInterface()) {
@@ -151,6 +156,104 @@ public static Object resolveAutowiringValue(Object autowiringValue, Class requir
151156
return autowiringValue;
152157
}
153158

159+
/**
160+
* Determine the target type for the generic return type of the given
161+
* <em>generic factory method</em>, where formal type variables are declared
162+
* on the given method itself.
163+
* <p>For example, given a factory method with the following signature,
164+
* if {@code resolveReturnTypeForGenericMethod()} is invoked with the reflected
165+
* method for {@code creatProxy()} and an {@code Object[]} array containing
166+
* {@code MyService.class}, {@code resolveReturnTypeForGenericMethod()} will
167+
* infer that the target return type is {@code MyService}.
168+
* <pre class="code">{@code public static <T> T createProxy(Class<T> clazz)}</pre>
169+
* <h4>Possible Return Values</h4>
170+
* <ul>
171+
* <li>the target return type, if it can be inferred</li>
172+
* <li>the {@linkplain Method#getReturnType() standard return type}, if
173+
* the given {@code method} does not declare any {@linkplain
174+
* Method#getTypeParameters() formal type variables}</li>
175+
* <li>the {@linkplain Method#getReturnType() standard return type}, if the
176+
* target return type cannot be inferred (e.g., due to type erasure)</li>
177+
* <li>{@code null}, if the length of the given arguments array is shorter
178+
* than the length of the {@linkplain
179+
* Method#getGenericParameterTypes() formal argument list} for the given
180+
* method</li>
181+
* </ul>
182+
* @param method the method to introspect (never {@code null})
183+
* @param args the arguments that will be supplied to the method when it is
184+
* invoked (never {@code null})
185+
* @param classLoader the ClassLoader to resolve class names against, if necessary
186+
* (never {@code null})
187+
* @return the resolved target return type, the standard return type, or {@code null}
188+
*/
189+
public static Class<?> resolveReturnTypeForFactoryMethod(Method method, Object[] args, ClassLoader classLoader) {
190+
Assert.notNull(method, "Method must not be null");
191+
Assert.notNull(args, "Argument array 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+
154257

155258
/**
156259
* 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+
}

0 commit comments

Comments
 (0)