提交 826e565b 编写于 作者: S Sam Brannen

Polish GenericTypeResolver

 - renamed resolveParameterizedReturnType() to
   resolveReturnTypeForGenericMethod()
 - fleshed out Javadoc for resolveReturnType() and
   resolveReturnTypeForGenericMethod() regarding declaration of formal
   type variables
 - improved wording in log statements and naming of local variables
   within resolveReturnTypeForGenericMethod()

Issue: SPR-9493
上级 3fbcebb8
...@@ -646,7 +646,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac ...@@ -646,7 +646,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac
&& factoryMethod.getName().equals(mbd.getFactoryMethodName()) && factoryMethod.getName().equals(mbd.getFactoryMethodName())
&& factoryMethod.getParameterTypes().length >= minNrOfArgs) { && factoryMethod.getParameterTypes().length >= minNrOfArgs) {
Class<?> returnType = GenericTypeResolver.resolveParameterizedReturnType(factoryMethod, args); Class<?> returnType = GenericTypeResolver.resolveReturnTypeForGenericMethod(factoryMethod, args);
if (returnType != null) { if (returnType != null) {
returnTypes.add(returnType); returnTypes.add(returnType);
} }
......
...@@ -96,12 +96,12 @@ public abstract class GenericTypeResolver { ...@@ -96,12 +96,12 @@ public abstract class GenericTypeResolver {
/** /**
* Determine the target type for the generic return type of the given method, * Determine the target type for the generic return type of the given method,
* where the type variable is declared on the given class. * where formal type variables are declared on the given class.
* *
* @param method the method to introspect * @param method the method to introspect
* @param clazz the class to resolve type variables against * @param clazz the class to resolve type variables against
* @return the corresponding generic parameter or return type * @return the corresponding generic parameter or return type
* @see #resolveParameterizedReturnType * @see #resolveReturnTypeForGenericMethod
*/ */
public static Class<?> resolveReturnType(Method method, Class<?> clazz) { public static Class<?> resolveReturnType(Method method, Class<?> clazz) {
Assert.notNull(method, "Method must not be null"); Assert.notNull(method, "Method must not be null");
...@@ -114,27 +114,27 @@ public abstract class GenericTypeResolver { ...@@ -114,27 +114,27 @@ public abstract class GenericTypeResolver {
/** /**
* Determine the target type for the generic return type of the given * Determine the target type for the generic return type of the given
* <em>parameterized</em> method, where the type variable is declared * <em>generic method</em>, where formal type variables are declared on
* on the given method. * the given method itself.
* *
* <p>For example, given a factory method with the following signature, * <p>For example, given a factory method with the following signature,
* if {@code resolveParameterizedReturnType()} is invoked with the reflected * if {@code resolveReturnTypeForGenericMethod()} is invoked with the reflected
* method for {@code creatProxy()} and an {@code Object[]} array containing * method for {@code creatProxy()} and an {@code Object[]} array containing
* {@code MyService.class}, {@code resolveParameterizedReturnType()} will * {@code MyService.class}, {@code resolveReturnTypeForGenericMethod()} will
* infer that the target return type is {@code MyService}. * infer that the target return type is {@code MyService}.
* *
* <pre>{@code public static <T> T createProxy(Class<T> clazz)}</pre> * <pre>{@code public static <T> T createProxy(Class<T> clazz)}</pre>
* *
* <h4>Possible Return Values</h4> * <h4>Possible Return Values</h4>
* <ul> * <ul>
* <li>the target return type if it can be inferred</li> * <li>the target return type, if it can be inferred</li>
* <li>the {@link Method#getReturnType() standard return type}, if * <li>the {@linkplain Method#getReturnType() standard return type}, if
* the given {@code method} does not declare any {@link * the given {@code method} does not declare any {@linkplain
* Method#getTypeParameters() generic types}</li> * Method#getTypeParameters() formal type variables}</li>
* <li>the {@link Method#getReturnType() standard return type}, if the * <li>the {@linkplain Method#getReturnType() standard return type}, if the
* target return type cannot be inferred (e.g., due to type erasure)</li> * target return type cannot be inferred (e.g., due to type erasure)</li>
* <li>{@code null}, if the length of the given arguments array is shorter * <li>{@code null}, if the length of the given arguments array is shorter
* than the length of the {@link * than the length of the {@linkplain
* Method#getGenericParameterTypes() formal argument list} for the given * Method#getGenericParameterTypes() formal argument list} for the given
* method</li> * method</li>
* </ul> * </ul>
...@@ -147,60 +147,59 @@ public abstract class GenericTypeResolver { ...@@ -147,60 +147,59 @@ public abstract class GenericTypeResolver {
* @since 3.2 * @since 3.2
* @see #resolveReturnType * @see #resolveReturnType
*/ */
public static Class<?> resolveParameterizedReturnType(Method method, Object[] args) { public static Class<?> resolveReturnTypeForGenericMethod(Method method, Object[] args) {
Assert.notNull(method, "method must not be null"); Assert.notNull(method, "method must not be null");
Assert.notNull(args, "args must not be null"); Assert.notNull(args, "args must not be null");
final TypeVariable<Method>[] declaredGenericTypes = method.getTypeParameters();
final Type genericReturnType = method.getGenericReturnType();
final Type[] genericArgumentTypes = method.getGenericParameterTypes();
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format("Resolving return type for [%s] with concrete method arguments [%s].",
"Resolving parameterized return type for [%s] with concrete method arguments [%s].",
method.toGenericString(), ObjectUtils.nullSafeToString(args))); method.toGenericString(), ObjectUtils.nullSafeToString(args)));
} }
// No declared generic types to inspect, so just return the standard return type. final TypeVariable<Method>[] declaredTypeVariables = method.getTypeParameters();
if (declaredGenericTypes.length == 0) { final Type genericReturnType = method.getGenericReturnType();
final Type[] methodArgumentTypes = method.getGenericParameterTypes();
// No declared type variables to inspect, so just return the standard return type.
if (declaredTypeVariables.length == 0) {
return method.getReturnType(); return method.getReturnType();
} }
// The supplied argument list is too short for the method's signature, so // The supplied argument list is too short for the method's signature, so
// return null, since such a method invocation would fail. // return null, since such a method invocation would fail.
if (args.length < genericArgumentTypes.length) { if (args.length < methodArgumentTypes.length) {
return null; return null;
} }
// Ensure that the generic type is declared directly on the method // Ensure that the type variable (e.g., T) is declared directly on the method
// itself, not on the enclosing class or interface. // itself (e.g., via <T>), not on the enclosing class or interface.
boolean locallyDeclaredGenericTypeMatchesReturnType = false; boolean locallyDeclaredTypeVariableMatchesReturnType = false;
for (TypeVariable<Method> currentType : declaredGenericTypes) { for (TypeVariable<Method> currentTypeVariable : declaredTypeVariables) {
if (currentType.equals(genericReturnType)) { if (currentTypeVariable.equals(genericReturnType)) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format(
"Found declared generic type [%s] that matches the target return type [%s].", "Found declared type variable [%s] that matches the target return type [%s].",
currentType, genericReturnType)); currentTypeVariable, genericReturnType));
} }
locallyDeclaredGenericTypeMatchesReturnType = true; locallyDeclaredTypeVariableMatchesReturnType = true;
break; break;
} }
} }
if (locallyDeclaredGenericTypeMatchesReturnType) { if (locallyDeclaredTypeVariableMatchesReturnType) {
for (int i = 0; i < genericArgumentTypes.length; i++) { for (int i = 0; i < methodArgumentTypes.length; i++) {
final Type currentArgumentType = genericArgumentTypes[i]; final Type currentMethodArgumentType = methodArgumentTypes[i];
if (currentArgumentType.equals(genericReturnType)) { if (currentMethodArgumentType.equals(genericReturnType)) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format(
"Found generic method argument at index [%s] that matches the target return type.", i)); "Found method argument type at index [%s] that matches the target return type.", i));
} }
return args[i].getClass(); return args[i].getClass();
} }
if (currentArgumentType instanceof ParameterizedType) { if (currentMethodArgumentType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) currentArgumentType; ParameterizedType parameterizedType = (ParameterizedType) currentMethodArgumentType;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
for (int j = 0; j < actualTypeArguments.length; j++) { for (int j = 0; j < actualTypeArguments.length; j++) {
...@@ -209,7 +208,7 @@ public abstract class GenericTypeResolver { ...@@ -209,7 +208,7 @@ public abstract class GenericTypeResolver {
if (typeArg.equals(genericReturnType)) { if (typeArg.equals(genericReturnType)) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format( logger.debug(String.format(
"Found method argument at index [%s] that is parameterized with a type that matches the target return type.", "Found method argument type at index [%s] that is parameterized with a type argument that matches the target return type.",
i)); i));
} }
...@@ -219,7 +218,7 @@ public abstract class GenericTypeResolver { ...@@ -219,7 +218,7 @@ public abstract class GenericTypeResolver {
// Consider adding logic to determine the class of the // Consider adding logic to determine the class of the
// J'th typeArg, if possible. // J'th typeArg, if possible.
logger.info(String.format( logger.info(String.format(
"Could not determine the target type for parameterized type [%s] for method [%s].", "Could not determine the target type for type argument [%s] for method [%s].",
typeArg, method.toGenericString())); typeArg, method.toGenericString()));
// For now, just fall back... // For now, just fall back...
......
...@@ -73,51 +73,51 @@ public class GenericTypeResolverTests { ...@@ -73,51 +73,51 @@ public class GenericTypeResolverTests {
* @since 3.2 * @since 3.2
*/ */
@Test @Test
public void parameterizedMethodReturnTypes() { public void genericMethodReturnTypes() {
Method notParameterized = findMethod(MyTypeWithMethods.class, "notParameterized", new Class[] {}); Method notParameterized = findMethod(MyTypeWithMethods.class, "notParameterized", new Class[] {});
assertEquals(String.class, resolveParameterizedReturnType(notParameterized, new Object[] {})); assertEquals(String.class, resolveReturnTypeForGenericMethod(notParameterized, new Object[] {}));
Method notParameterizedWithArguments = findMethod(MyTypeWithMethods.class, "notParameterizedWithArguments", Method notParameterizedWithArguments = findMethod(MyTypeWithMethods.class, "notParameterizedWithArguments",
new Class[] { Integer.class, Boolean.class }); new Class[] { Integer.class, Boolean.class });
assertEquals(String.class, assertEquals(String.class,
resolveParameterizedReturnType(notParameterizedWithArguments, new Object[] { 99, true })); resolveReturnTypeForGenericMethod(notParameterizedWithArguments, new Object[] { 99, true }));
Method createProxy = findMethod(MyTypeWithMethods.class, "createProxy", new Class[] { Object.class }); Method createProxy = findMethod(MyTypeWithMethods.class, "createProxy", new Class[] { Object.class });
assertEquals(String.class, resolveParameterizedReturnType(createProxy, new Object[] { "foo" })); assertEquals(String.class, resolveReturnTypeForGenericMethod(createProxy, new Object[] { "foo" }));
Method createNamedProxyWithDifferentTypes = findMethod(MyTypeWithMethods.class, "createNamedProxy", Method createNamedProxyWithDifferentTypes = findMethod(MyTypeWithMethods.class, "createNamedProxy",
new Class[] { String.class, Object.class }); new Class[] { String.class, Object.class });
// one argument to few // one argument to few
assertNull(resolveParameterizedReturnType(createNamedProxyWithDifferentTypes, new Object[] { "enigma" })); assertNull(resolveReturnTypeForGenericMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma" }));
assertEquals(Long.class, assertEquals(Long.class,
resolveParameterizedReturnType(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L })); resolveReturnTypeForGenericMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L }));
Method createNamedProxyWithDuplicateTypes = findMethod(MyTypeWithMethods.class, "createNamedProxy", Method createNamedProxyWithDuplicateTypes = findMethod(MyTypeWithMethods.class, "createNamedProxy",
new Class[] { String.class, Object.class }); new Class[] { String.class, Object.class });
assertEquals(String.class, assertEquals(String.class,
resolveParameterizedReturnType(createNamedProxyWithDuplicateTypes, new Object[] { "enigma", "foo" })); resolveReturnTypeForGenericMethod(createNamedProxyWithDuplicateTypes, new Object[] { "enigma", "foo" }));
Method createMock = findMethod(MyTypeWithMethods.class, "createMock", new Class[] { Class.class }); Method createMock = findMethod(MyTypeWithMethods.class, "createMock", new Class[] { Class.class });
assertEquals(Runnable.class, resolveParameterizedReturnType(createMock, new Object[] { Runnable.class })); assertEquals(Runnable.class, resolveReturnTypeForGenericMethod(createMock, new Object[] { Runnable.class }));
Method createNamedMock = findMethod(MyTypeWithMethods.class, "createNamedMock", new Class[] { String.class, Method createNamedMock = findMethod(MyTypeWithMethods.class, "createNamedMock", new Class[] { String.class,
Class.class }); Class.class });
assertEquals(Runnable.class, assertEquals(Runnable.class,
resolveParameterizedReturnType(createNamedMock, new Object[] { "foo", Runnable.class })); resolveReturnTypeForGenericMethod(createNamedMock, new Object[] { "foo", Runnable.class }));
Method createVMock = findMethod(MyTypeWithMethods.class, "createVMock", Method createVMock = findMethod(MyTypeWithMethods.class, "createVMock",
new Class[] { Object.class, Class.class }); new Class[] { Object.class, Class.class });
assertEquals(Runnable.class, assertEquals(Runnable.class,
resolveParameterizedReturnType(createVMock, new Object[] { "foo", Runnable.class })); resolveReturnTypeForGenericMethod(createVMock, new Object[] { "foo", Runnable.class }));
// Ideally we would expect String.class instead of Object.class, but // Ideally we would expect String.class instead of Object.class, but
// resolveParameterizedReturnType() does not currently support this form of // resolveReturnTypeForGenericMethod() does not currently support this form of
// look-up. // look-up.
Method extractValueFrom = findMethod(MyTypeWithMethods.class, "extractValueFrom", Method extractValueFrom = findMethod(MyTypeWithMethods.class, "extractValueFrom",
new Class[] { MyInterfaceType.class }); new Class[] { MyInterfaceType.class });
assertEquals(Object.class, assertEquals(Object.class,
resolveParameterizedReturnType(extractValueFrom, new Object[] { new MySimpleInterfaceType() })); resolveReturnTypeForGenericMethod(extractValueFrom, new Object[] { new MySimpleInterfaceType() }));
// Ideally we would expect Boolean.class instead of Object.class, but this // Ideally we would expect Boolean.class instead of Object.class, but this
// information is not available at run-time due to type erasure. // information is not available at run-time due to type erasure.
...@@ -125,7 +125,7 @@ public class GenericTypeResolverTests { ...@@ -125,7 +125,7 @@ public class GenericTypeResolverTests {
map.put(0, false); map.put(0, false);
map.put(1, true); map.put(1, true);
Method extractMagicValue = findMethod(MyTypeWithMethods.class, "extractMagicValue", new Class[] { Map.class }); Method extractMagicValue = findMethod(MyTypeWithMethods.class, "extractMagicValue", new Class[] { Map.class });
assertEquals(Object.class, resolveParameterizedReturnType(extractMagicValue, new Object[] { map })); assertEquals(Object.class, resolveReturnTypeForGenericMethod(extractMagicValue, new Object[] { map }));
} }
......
...@@ -7,7 +7,7 @@ Changes in version 3.2 M2 (2012-08-xx) ...@@ -7,7 +7,7 @@ Changes in version 3.2 M2 (2012-08-xx)
-------------------------------------- --------------------------------------
* spring-test module now depends on junit:junit-dep (SPR-6966) * spring-test module now depends on junit:junit-dep (SPR-6966)
* now inferring return type of parameterized factory methods (SPR-9493) * now inferring return type of generic factory methods (SPR-9493)
* SpEL Tokenizer now supports methods on integers (SPR-9612) * SpEL Tokenizer now supports methods on integers (SPR-9612)
* introduced support for case-insensitive null literals in SpEL expressions (SPR-9613) * introduced support for case-insensitive null literals in SpEL expressions (SPR-9613)
* now using BufferedInputStream in SimpleMetaDataReader to double performance (SPR-9528) * now using BufferedInputStream in SimpleMetaDataReader to double performance (SPR-9528)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册