diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index 4a67ca6869522ed0dad402a049484be3d49b4fe5..c3369ef1191feb7cc29d7e288c8f19408ef50fcd 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -646,7 +646,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac && factoryMethod.getName().equals(mbd.getFactoryMethodName()) && factoryMethod.getParameterTypes().length >= minNrOfArgs) { - Class returnType = GenericTypeResolver.resolveParameterizedReturnType(factoryMethod, args); + Class returnType = GenericTypeResolver.resolveReturnTypeForGenericMethod(factoryMethod, args); if (returnType != null) { returnTypes.add(returnType); } diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java index d4dda19c52b4a2552b137458b764fc0da89c78c4..9c025c0ba5d067656acd2305cdf765a8d68373db 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -96,12 +96,12 @@ public abstract class GenericTypeResolver { /** * 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 clazz the class to resolve type variables against * @return the corresponding generic parameter or return type - * @see #resolveParameterizedReturnType + * @see #resolveReturnTypeForGenericMethod */ public static Class resolveReturnType(Method method, Class clazz) { Assert.notNull(method, "Method must not be null"); @@ -114,27 +114,27 @@ public abstract class GenericTypeResolver { /** * Determine the target type for the generic return type of the given - * parameterized method, where the type variable is declared - * on the given method. + * generic method, where formal type variables are declared on + * the given method itself. * *

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 - * {@code MyService.class}, {@code resolveParameterizedReturnType()} will + * {@code MyService.class}, {@code resolveReturnTypeForGenericMethod()} will * infer that the target return type is {@code MyService}. * *

{@code public static  T createProxy(Class clazz)}
* *

Possible Return Values

* @@ -147,60 +147,59 @@ public abstract class GenericTypeResolver { * @since 3.2 * @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(args, "args must not be null"); - final TypeVariable[] declaredGenericTypes = method.getTypeParameters(); - final Type genericReturnType = method.getGenericReturnType(); - final Type[] genericArgumentTypes = method.getGenericParameterTypes(); - if (logger.isDebugEnabled()) { - logger.debug(String.format( - "Resolving parameterized return type for [%s] with concrete method arguments [%s].", + logger.debug(String.format("Resolving return type for [%s] with concrete method arguments [%s].", method.toGenericString(), ObjectUtils.nullSafeToString(args))); } - // No declared generic types to inspect, so just return the standard return type. - if (declaredGenericTypes.length == 0) { + final TypeVariable[] declaredTypeVariables = method.getTypeParameters(); + 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(); } // The supplied argument list is too short for the method's signature, so // return null, since such a method invocation would fail. - if (args.length < genericArgumentTypes.length) { + if (args.length < methodArgumentTypes.length) { return null; } - // Ensure that the generic type is declared directly on the method - // itself, not on the enclosing class or interface. - boolean locallyDeclaredGenericTypeMatchesReturnType = false; - for (TypeVariable currentType : declaredGenericTypes) { - if (currentType.equals(genericReturnType)) { + // Ensure that the type variable (e.g., T) is declared directly on the method + // itself (e.g., via ), not on the enclosing class or interface. + boolean locallyDeclaredTypeVariableMatchesReturnType = false; + for (TypeVariable currentTypeVariable : declaredTypeVariables) { + if (currentTypeVariable.equals(genericReturnType)) { if (logger.isDebugEnabled()) { logger.debug(String.format( - "Found declared generic type [%s] that matches the target return type [%s].", - currentType, genericReturnType)); + "Found declared type variable [%s] that matches the target return type [%s].", + currentTypeVariable, genericReturnType)); } - locallyDeclaredGenericTypeMatchesReturnType = true; + locallyDeclaredTypeVariableMatchesReturnType = true; break; } } - if (locallyDeclaredGenericTypeMatchesReturnType) { - for (int i = 0; i < genericArgumentTypes.length; i++) { - final Type currentArgumentType = genericArgumentTypes[i]; + if (locallyDeclaredTypeVariableMatchesReturnType) { + for (int i = 0; i < methodArgumentTypes.length; i++) { + final Type currentMethodArgumentType = methodArgumentTypes[i]; - if (currentArgumentType.equals(genericReturnType)) { + if (currentMethodArgumentType.equals(genericReturnType)) { if (logger.isDebugEnabled()) { 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(); } - if (currentArgumentType instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) currentArgumentType; + if (currentMethodArgumentType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) currentMethodArgumentType; Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); for (int j = 0; j < actualTypeArguments.length; j++) { @@ -209,7 +208,7 @@ public abstract class GenericTypeResolver { if (typeArg.equals(genericReturnType)) { if (logger.isDebugEnabled()) { 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)); } @@ -219,7 +218,7 @@ public abstract class GenericTypeResolver { // Consider adding logic to determine the class of the // J'th typeArg, if possible. 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())); // For now, just fall back... diff --git a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java index 338529a48986c70c2cf1f5adddc73d28bd8f95dd..344dcef5418bddc0cf8852142e0e625c7589e79d 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -73,51 +73,51 @@ public class GenericTypeResolverTests { * @since 3.2 */ @Test - public void parameterizedMethodReturnTypes() { + public void genericMethodReturnTypes() { 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", new Class[] { Integer.class, Boolean.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 }); - assertEquals(String.class, resolveParameterizedReturnType(createProxy, new Object[] { "foo" })); + assertEquals(String.class, resolveReturnTypeForGenericMethod(createProxy, new Object[] { "foo" })); Method createNamedProxyWithDifferentTypes = findMethod(MyTypeWithMethods.class, "createNamedProxy", new Class[] { String.class, Object.class }); // one argument to few - assertNull(resolveParameterizedReturnType(createNamedProxyWithDifferentTypes, new Object[] { "enigma" })); + assertNull(resolveReturnTypeForGenericMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma" })); assertEquals(Long.class, - resolveParameterizedReturnType(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L })); + resolveReturnTypeForGenericMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L })); Method createNamedProxyWithDuplicateTypes = findMethod(MyTypeWithMethods.class, "createNamedProxy", new Class[] { String.class, Object.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 }); - 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, Class.class }); assertEquals(Runnable.class, - resolveParameterizedReturnType(createNamedMock, new Object[] { "foo", Runnable.class })); + resolveReturnTypeForGenericMethod(createNamedMock, new Object[] { "foo", Runnable.class })); Method createVMock = findMethod(MyTypeWithMethods.class, "createVMock", new Class[] { Object.class, Class.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 - // resolveParameterizedReturnType() does not currently support this form of + // resolveReturnTypeForGenericMethod() does not currently support this form of // look-up. Method extractValueFrom = findMethod(MyTypeWithMethods.class, "extractValueFrom", new Class[] { MyInterfaceType.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 // information is not available at run-time due to type erasure. @@ -125,7 +125,7 @@ public class GenericTypeResolverTests { map.put(0, false); map.put(1, true); 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 })); } diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index 9f1766ee2ebe5d86366ce0f0b16bd0ebc4449166..bcb7659bd66e5d4de3dd428548c0e10075371576 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -7,7 +7,7 @@ Changes in version 3.2 M2 (2012-08-xx) -------------------------------------- * 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) * introduced support for case-insensitive null literals in SpEL expressions (SPR-9613) * now using BufferedInputStream in SimpleMetaDataReader to double performance (SPR-9528)