diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java index 1e59e7d134..5bf2fffcdf 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.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. @@ -146,7 +146,7 @@ public class ConstructorArgumentValues { * untyped values only) * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getIndexedArgumentValue(int index, Class requiredType) { + public ValueHolder getIndexedArgumentValue(int index, Class requiredType) { return getIndexedArgumentValue(index, requiredType, null); } @@ -159,7 +159,7 @@ public class ConstructorArgumentValues { * unnamed values only) * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getIndexedArgumentValue(int index, Class requiredType, String requiredName) { + public ValueHolder getIndexedArgumentValue(int index, Class requiredType, String requiredName) { Assert.isTrue(index >= 0, "Index must not be negative"); ValueHolder valueHolder = this.indexedArgumentValues.get(index); if (valueHolder != null && @@ -247,7 +247,7 @@ public class ConstructorArgumentValues { * @param requiredType the type to match * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getGenericArgumentValue(Class requiredType) { + public ValueHolder getGenericArgumentValue(Class requiredType) { return getGenericArgumentValue(requiredType, null, null); } @@ -257,7 +257,7 @@ public class ConstructorArgumentValues { * @param requiredName the name to match * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName) { + public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName) { return getGenericArgumentValue(requiredType, requiredName, null); } @@ -273,7 +273,7 @@ public class ConstructorArgumentValues { * in the current resolution process and should therefore not be returned again * @return the ValueHolder for the argument, or {@code null} if none found */ - public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName, Set usedValueHolders) { + public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName, Set usedValueHolders) { for (ValueHolder valueHolder : this.genericArgumentValues) { if (usedValueHolders != null && usedValueHolders.contains(valueHolder)) { continue; @@ -309,10 +309,10 @@ public class ConstructorArgumentValues { * Look for an argument value that either corresponds to the given index * in the constructor argument list or generically matches by type. * @param index the index in the constructor argument list - * @param requiredType the type to match + * @param requiredType the parameter type to match * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getArgumentValue(int index, Class requiredType) { + public ValueHolder getArgumentValue(int index, Class requiredType) { return getArgumentValue(index, requiredType, null, null); } @@ -320,11 +320,11 @@ public class ConstructorArgumentValues { * Look for an argument value that either corresponds to the given index * in the constructor argument list or generically matches by type. * @param index the index in the constructor argument list - * @param requiredType the type to match - * @param requiredName the name to match + * @param requiredType the parameter type to match + * @param requiredName the parameter name to match * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getArgumentValue(int index, Class requiredType, String requiredName) { + public ValueHolder getArgumentValue(int index, Class requiredType, String requiredName) { return getArgumentValue(index, requiredType, requiredName, null); } @@ -332,15 +332,17 @@ public class ConstructorArgumentValues { * Look for an argument value that either corresponds to the given index * in the constructor argument list or generically matches by type. * @param index the index in the constructor argument list - * @param requiredType the type to match (can be {@code null} to find - * an untyped argument value) + * @param requiredType the parameter type to match (can be {@code null} + * to find an untyped argument value) + * @param requiredName the parameter name to match (can be {@code null} + * to find an unnamed argument value) * @param usedValueHolders a Set of ValueHolder objects that have already * been used in the current resolution process and should therefore not * be returned again (allowing to return the next generic argument match * in case of multiple generic argument values of the same type) * @return the ValueHolder for the argument, or {@code null} if none set */ - public ValueHolder getArgumentValue(int index, Class requiredType, String requiredName, Set usedValueHolders) { + public ValueHolder getArgumentValue(int index, Class requiredType, String requiredName, Set usedValueHolders) { Assert.isTrue(index >= 0, "Index must not be negative"); ValueHolder valueHolder = getIndexedArgumentValue(index, requiredType, requiredName); if (valueHolder == null) { 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 846c359d68..25c8bb893d 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 @@ -63,7 +63,7 @@ import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableBeanFactory; -import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder; +import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor; @@ -632,12 +632,6 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac return null; } - List argumentValues = mbd.getConstructorArgumentValues().getGenericArgumentValues(); - Object[] args = new Object[argumentValues.size()]; - for (int i = 0; i < args.length; i++) { - args[i] = argumentValues.get(i).getValue(); - } - // If all factory methods have the same return type, return that type. // Can't clearly figure out exact method due to type converting / autowiring! int minNrOfArgs = mbd.getConstructorArgumentValues().getArgumentCount(); @@ -647,6 +641,27 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic && factoryMethod.getName().equals(mbd.getFactoryMethodName()) && factoryMethod.getParameterTypes().length >= minNrOfArgs) { + Class[] paramTypes = factoryMethod.getParameterTypes(); + String[] paramNames = null; + ParameterNameDiscoverer pnd = getParameterNameDiscoverer(); + if (pnd != null) { + paramNames = pnd.getParameterNames(factoryMethod); + } + ConstructorArgumentValues cav = mbd.getConstructorArgumentValues(); + Set usedValueHolders = + new HashSet(paramTypes.length); + Object[] args = new Object[paramTypes.length]; + for (int i = 0; i < args.length; i++) { + ConstructorArgumentValues.ValueHolder valueHolder = cav.getArgumentValue( + i, paramTypes[i], (paramNames != null ? paramNames[i] : null), usedValueHolders); + if (valueHolder == null) { + valueHolder = cav.getGenericArgumentValue(null, null, usedValueHolders); + } + if (valueHolder != null) { + args[i] = valueHolder.getValue(); + usedValueHolders.add(valueHolder); + } + } Class returnType = AutowireUtils.resolveReturnTypeForFactoryMethod( factoryMethod, args, getBeanClassLoader()); if (returnType != null) { diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java index 2480f64fac..99768b6552 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java @@ -16,13 +16,9 @@ package org.springframework.beans.factory.support; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; @@ -37,6 +33,7 @@ import java.util.Set; import org.junit.Test; import org.mockito.Mockito; + import org.springframework.beans.PropertyEditorRegistrar; import org.springframework.beans.PropertyEditorRegistry; import org.springframework.beans.factory.BeanCreationException; @@ -46,12 +43,12 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.UrlResource; import org.springframework.tests.Assume; import org.springframework.tests.TestGroup; - import org.springframework.tests.sample.beans.GenericBean; import org.springframework.tests.sample.beans.GenericIntegerBean; import org.springframework.tests.sample.beans.GenericSetOfIntegerBean; import org.springframework.tests.sample.beans.TestBean; +import static org.junit.Assert.*; /** * @author Juergen Hoeller @@ -115,7 +112,7 @@ public class BeanFactoryGenericsTests { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); RootBeanDefinition rbd = new RootBeanDefinition(GenericIntegerBean.class); - List input = new ArrayList(); + List input = new ArrayList(); input.add(1); rbd.getPropertyValues().add("testBeanList", input); @@ -655,18 +652,22 @@ public class BeanFactoryGenericsTests { } /** - * Tests support for parameterized {@code factory-method} declarations such - * as Mockito {@code mock()} method which has the following signature. - * - *
{@code
+	 * Tests support for parameterized static {@code factory-method} declarations such as
+	 * Mockito's {@code mock()} method which has the following signature.
+	 * 
+	 * 
+	 * {@code
 	 * public static  T mock(Class classToMock)
-	 * }
- * + * } + *
+ * + *

* See SPR-9493 + * * @since 3.2 */ @Test - public void parameterizedFactoryMethod() { + public void parameterizedStaticFactoryMethod() { RootBeanDefinition rbd = new RootBeanDefinition(Mockito.class); rbd.setFactoryMethodName("mock"); rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); @@ -678,6 +679,71 @@ public class BeanFactoryGenericsTests { assertEquals(1, beans.size()); } + /** + * Tests support for parameterized instance {@code factory-method} declarations such + * as EasyMock's {@code IMocksControl.createMock()} method which has the following + * signature. + *

+	 * {@code
+	 * public  T createMock(Class toMock)
+	 * }
+	 * 
+ *

See SPR-10411 + */ + @Test + public void parameterizedInstanceFactoryMethod() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", rbd); + + rbd = new RootBeanDefinition(); + rbd.setFactoryBeanName("mocksControl"); + rbd.setFactoryMethodName("createMock"); + rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class); + + bf.registerBeanDefinition("mock", rbd); + + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(1, beans.size()); + } + + @Test + public void parameterizedInstanceFactoryMethodWithNonResolvedClassName() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", rbd); + + rbd = new RootBeanDefinition(); + rbd.setFactoryBeanName("mocksControl"); + rbd.setFactoryMethodName("createMock"); + rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class.getName()); + + bf.registerBeanDefinition("mock", rbd); + + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(1, beans.size()); + } + + @Test + public void parameterizedInstanceFactoryMethodWithIndexedArgument() { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + + RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class); + bf.registerBeanDefinition("mocksControl", rbd); + + rbd = new RootBeanDefinition(); + rbd.setFactoryBeanName("mocksControl"); + rbd.setFactoryMethodName("createMock"); + rbd.getConstructorArgumentValues().addIndexedArgumentValue(0, Runnable.class); + + bf.registerBeanDefinition("mock", rbd); + + Map beans = bf.getBeansOfType(Runnable.class); + assertEquals(1, beans.size()); + } + @SuppressWarnings("serial") public static class NamedUrlList extends LinkedList { @@ -722,4 +788,25 @@ public class BeanFactoryGenericsTests { } } + /** + * Pseudo-implementation of EasyMock's {@code MocksControl} class. + */ + public static class MocksControl { + + @SuppressWarnings("unchecked") + public T createMock(Class toMock) { + + return (T) Proxy.newProxyInstance( + BeanFactoryGenericsTests.class.getClassLoader(), + new Class[] { toMock }, new InvocationHandler() { + + @Override + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + throw new UnsupportedOperationException("mocked!"); + } + }); + } + } + }