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 5636758042..7e4f33d0c2 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 @@ -665,7 +665,8 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic && factoryMethod.getName().equals(mbd.getFactoryMethodName()) && factoryMethod.getParameterTypes().length >= minNrOfArgs) { - Class returnType = GenericTypeResolver.resolveReturnTypeForGenericMethod(factoryMethod, args); + Class returnType = AutowireUtils.resolveReturnTypeForFactoryMethod( + factoryMethod, args, getBeanClassLoader()); if (returnType != null) { returnTypes.add(returnType); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java index 140e11569c..d2d7577640 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AutowireUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,12 +23,16 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.Arrays; import java.util.Comparator; import java.util.Set; import org.springframework.beans.factory.ObjectFactory; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** @@ -37,6 +41,7 @@ import org.springframework.util.ClassUtils; * * @author Juergen Hoeller * @author Mark Fisher + * @author Sam Brannen * @since 1.1.2 * @see AbstractAutowireCapableBeanFactory */ @@ -119,8 +124,8 @@ abstract class AutowireUtils { public static boolean isSetterDefinedInInterface(PropertyDescriptor pd, Set interfaces) { Method setter = pd.getWriteMethod(); if (setter != null) { - Class targetClass = setter.getDeclaringClass(); - for (Class ifc : interfaces) { + Class targetClass = setter.getDeclaringClass(); + for (Class ifc : interfaces) { if (ifc.isAssignableFrom(targetClass) && ClassUtils.hasMethod(ifc, setter.getName(), setter.getParameterTypes())) { return true; @@ -137,7 +142,7 @@ abstract class AutowireUtils { * @param requiredType the type to assign the result to * @return the resolved value */ - public static Object resolveAutowiringValue(Object autowiringValue, Class requiredType) { + public static Object resolveAutowiringValue(Object autowiringValue, Class requiredType) { if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) { ObjectFactory factory = (ObjectFactory) autowiringValue; if (autowiringValue instanceof Serializable && requiredType.isInterface()) { @@ -151,6 +156,104 @@ abstract class AutowireUtils { return autowiringValue; } + /** + * Determine the target type for the generic return type of the given + * generic factory method, where formal type variables are declared + * on the given method itself. + *

For example, given a factory method with the following signature, + * if {@code resolveReturnTypeForGenericMethod()} is invoked with the reflected + * method for {@code creatProxy()} and an {@code Object[]} array containing + * {@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

+ * + * @param method the method to introspect (never {@code null}) + * @param args the arguments that will be supplied to the method when it is + * invoked (never {@code null}) + * @param classLoader the ClassLoader to resolve class names against, if necessary + * (never {@code null}) + * @return the resolved target return type, the standard return type, or {@code null} + */ + public static Class resolveReturnTypeForFactoryMethod(Method method, Object[] args, ClassLoader classLoader) { + Assert.notNull(method, "Method must not be null"); + Assert.notNull(args, "Argument array must not be null"); + + TypeVariable[] declaredTypeVariables = method.getTypeParameters(); + Type genericReturnType = method.getGenericReturnType(); + 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 < methodArgumentTypes.length) { + return null; + } + + // 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)) { + locallyDeclaredTypeVariableMatchesReturnType = true; + break; + } + } + + if (locallyDeclaredTypeVariableMatchesReturnType) { + for (int i = 0; i < methodArgumentTypes.length; i++) { + Type currentMethodArgumentType = methodArgumentTypes[i]; + if (currentMethodArgumentType.equals(genericReturnType)) { + return args[i].getClass(); + } + if (currentMethodArgumentType instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) currentMethodArgumentType; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + for (Type typeArg : actualTypeArguments) { + if (typeArg.equals(genericReturnType)) { + Object arg = args[i]; + if (arg instanceof Class) { + return (Class) arg; + } + else if (arg instanceof String) { + try { + return classLoader.loadClass((String) arg); + } + catch (ClassNotFoundException ex) { + throw new IllegalStateException( + "Could not resolve specified class name argument [" + arg + "]", ex); + } + } + else { + // Consider adding logic to determine the class of the typeArg, if possible. + // For now, just fall back... + return method.getReturnType(); + } + } + } + } + } + } + + // Fall back... + return method.getReturnType(); + } + /** * Reflective InvocationHandler for lazy access to the current target object. diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java new file mode 100644 index 0000000000..9a834424d6 --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/AutowireUtilsTests.java @@ -0,0 +1,195 @@ +/* + * Copyright 2002-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.beans.factory.support; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.util.ReflectionUtils; + +import static org.junit.Assert.*; + +/** + * @author Juergen Hoeller + * @author Sam Brannen + */ +public class AutowireUtilsTests { + + @Test + public void genericMethodReturnTypes() { + Method notParameterized = ReflectionUtils.findMethod(MyTypeWithMethods.class, "notParameterized", new Class[]{}); + assertEquals(String.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterized, new Object[]{}, getClass().getClassLoader())); + + Method notParameterizedWithArguments = ReflectionUtils.findMethod(MyTypeWithMethods.class, "notParameterizedWithArguments", + new Class[] { Integer.class, Boolean.class }); + assertEquals(String.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(notParameterizedWithArguments, new Object[] { 99, true }, getClass().getClassLoader())); + + Method createProxy = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createProxy", new Class[] { Object.class }); + assertEquals(String.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createProxy, new Object[] { "foo" }, getClass().getClassLoader())); + + Method createNamedProxyWithDifferentTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy", + new Class[] { String.class, Object.class }); + // one argument to few + assertNull( + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDifferentTypes, new Object[]{"enigma"}, getClass().getClassLoader())); + assertEquals(Long.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L }, getClass().getClassLoader())); + + Method createNamedProxyWithDuplicateTypes = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedProxy", + new Class[] { String.class, Object.class }); + assertEquals(String.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedProxyWithDuplicateTypes, new Object[] { "enigma", "foo" }, getClass().getClassLoader())); + + Method createMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createMock", new Class[] { Class.class }); + assertEquals(Runnable.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[] { Runnable.class }, getClass().getClassLoader())); + assertEquals(Runnable.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createMock, new Object[] { Runnable.class.getName() }, getClass().getClassLoader())); + + Method createNamedMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createNamedMock", new Class[] { String.class, + Class.class }); + assertEquals(Runnable.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createNamedMock, new Object[] { "foo", Runnable.class }, getClass().getClassLoader())); + + Method createVMock = ReflectionUtils.findMethod(MyTypeWithMethods.class, "createVMock", + new Class[] { Object.class, Class.class }); + assertEquals(Runnable.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(createVMock, new Object[] { "foo", Runnable.class }, getClass().getClassLoader())); + + // Ideally we would expect String.class instead of Object.class, but + // resolveReturnTypeForFactoryMethod() does not currently support this form of + // look-up. + Method extractValueFrom = ReflectionUtils.findMethod(MyTypeWithMethods.class, "extractValueFrom", + new Class[] { MyInterfaceType.class }); + assertEquals(Object.class, + AutowireUtils.resolveReturnTypeForFactoryMethod(extractValueFrom, new Object[] { new MySimpleInterfaceType() }, getClass().getClassLoader())); + + // Ideally we would expect Boolean.class instead of Object.class, but this + // information is not available at run-time due to type erasure. + Map map = new HashMap(); + map.put(0, false); + map.put(1, true); + Method extractMagicValue = ReflectionUtils.findMethod(MyTypeWithMethods.class, "extractMagicValue", new Class[] { Map.class }); + assertEquals(Object.class, AutowireUtils.resolveReturnTypeForFactoryMethod(extractMagicValue, new Object[] { map }, getClass().getClassLoader())); + } + + + public interface MyInterfaceType { + } + + public class MySimpleInterfaceType implements MyInterfaceType { + } + + public static class MyTypeWithMethods { + + public MyInterfaceType integer() { + return null; + } + + public MySimpleInterfaceType string() { + return null; + } + + public Object object() { + return null; + } + + @SuppressWarnings("rawtypes") + public MyInterfaceType raw() { + return null; + } + + public String notParameterized() { + return null; + } + + public String notParameterizedWithArguments(Integer x, Boolean b) { + return null; + } + + /** + * Simulates a factory method that wraps the supplied object in a proxy of the + * same type. + */ + public static T createProxy(T object) { + return null; + } + + /** + * Similar to {@link #createProxy(Object)} but adds an additional argument before + * the argument of type {@code T}. Note that they may potentially be of the same + * time when invoked! + */ + public static T createNamedProxy(String name, T object) { + return null; + } + + /** + * Simulates factory methods found in libraries such as Mockito and EasyMock. + */ + public static MOCK createMock(Class toMock) { + return null; + } + + /** + * Similar to {@link #createMock(Class)} but adds an additional method argument + * before the parameterized argument. + */ + public static T createNamedMock(String name, Class toMock) { + return null; + } + + /** + * Similar to {@link #createNamedMock(String, Class)} but adds an additional + * parameterized type. + */ + public static T createVMock(V name, Class toMock) { + return null; + } + + /** + * Extract some value of the type supported by the interface (i.e., by a concrete, + * non-generic implementation of the interface). + */ + public static T extractValueFrom(MyInterfaceType myInterfaceType) { + return null; + } + + /** + * Extract some magic value from the supplied map. + */ + public static V extractMagicValue(Map map) { + return null; + } + + public void readIntegerInputMessage(MyInterfaceType message) { + } + + public void readIntegerArrayInputMessage(MyInterfaceType[] message) { + } + + public void readGenericArrayInputMessage(T[] message) { + } + } + +} 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 53136acd9a..b7cb02c883 100644 --- a/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/GenericTypeResolverTests.java @@ -73,67 +73,6 @@ public class GenericTypeResolverTests { resolveReturnTypeArgument(findMethod(MyTypeWithMethods.class, "object"), MyInterfaceType.class)); } - /** - * @since 3.2 - */ - @Test - public void genericMethodReturnTypes() { - Method notParameterized = findMethod(MyTypeWithMethods.class, "notParameterized", new Class[] {}); - assertEquals(String.class, resolveReturnTypeForGenericMethod(notParameterized, new Object[] {})); - - Method notParameterizedWithArguments = findMethod(MyTypeWithMethods.class, "notParameterizedWithArguments", - new Class[] { Integer.class, Boolean.class }); - assertEquals(String.class, - resolveReturnTypeForGenericMethod(notParameterizedWithArguments, new Object[] { 99, true })); - - Method createProxy = findMethod(MyTypeWithMethods.class, "createProxy", new Class[] { Object.class }); - 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(resolveReturnTypeForGenericMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma" })); - assertEquals(Long.class, - resolveReturnTypeForGenericMethod(createNamedProxyWithDifferentTypes, new Object[] { "enigma", 99L })); - - Method createNamedProxyWithDuplicateTypes = findMethod(MyTypeWithMethods.class, "createNamedProxy", - new Class[] { String.class, Object.class }); - assertEquals(String.class, - resolveReturnTypeForGenericMethod(createNamedProxyWithDuplicateTypes, new Object[] { "enigma", "foo" })); - - Method createMock = findMethod(MyTypeWithMethods.class, "createMock", new Class[] { Class.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, - resolveReturnTypeForGenericMethod(createNamedMock, new Object[] { "foo", Runnable.class })); - - Method createVMock = findMethod(MyTypeWithMethods.class, "createVMock", - new Class[] { Object.class, Class.class }); - assertEquals(Runnable.class, - resolveReturnTypeForGenericMethod(createVMock, new Object[] { "foo", Runnable.class })); - - // Ideally we would expect String.class instead of Object.class, but - // resolveReturnTypeForGenericMethod() does not currently support this form of - // look-up. - Method extractValueFrom = findMethod(MyTypeWithMethods.class, "extractValueFrom", - new Class[] { MyInterfaceType.class }); - assertEquals(Object.class, - 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. - Map map = new HashMap(); - map.put(0, false); - map.put(1, true); - Method extractMagicValue = findMethod(MyTypeWithMethods.class, "extractMagicValue", new Class[] { Map.class }); - assertEquals(Object.class, resolveReturnTypeForGenericMethod(extractMagicValue, new Object[] { map })); - } - - /** - * @since 3.2 - */ @Test public void testResolveType() { Method intMessageMethod = findMethod(MyTypeWithMethods.class, "readIntegerInputMessage", MyInterfaceType.class); @@ -197,6 +136,7 @@ public class GenericTypeResolverTests { assertThat(x, equalTo((Type) Long.class)); } + public interface MyInterfaceType { }