Skip to content

Commit 42568af

Browse files
committed
Factory method type resolution works with indexed and named arguments as well
Issue: SPR-11019 (cherry picked from commit 109faac)
1 parent ce001c2 commit 42568af

File tree

3 files changed

+141
-37
lines changed

3 files changed

+141
-37
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/config/ConstructorArgumentValues.java

Lines changed: 16 additions & 14 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.
@@ -146,7 +146,7 @@ public boolean hasIndexedArgumentValue(int index) {
146146
* untyped values only)
147147
* @return the ValueHolder for the argument, or {@code null} if none set
148148
*/
149-
public ValueHolder getIndexedArgumentValue(int index, Class requiredType) {
149+
public ValueHolder getIndexedArgumentValue(int index, Class<?> requiredType) {
150150
return getIndexedArgumentValue(index, requiredType, null);
151151
}
152152

@@ -159,7 +159,7 @@ public ValueHolder getIndexedArgumentValue(int index, Class requiredType) {
159159
* unnamed values only)
160160
* @return the ValueHolder for the argument, or {@code null} if none set
161161
*/
162-
public ValueHolder getIndexedArgumentValue(int index, Class requiredType, String requiredName) {
162+
public ValueHolder getIndexedArgumentValue(int index, Class<?> requiredType, String requiredName) {
163163
Assert.isTrue(index >= 0, "Index must not be negative");
164164
ValueHolder valueHolder = this.indexedArgumentValues.get(index);
165165
if (valueHolder != null &&
@@ -247,7 +247,7 @@ private void addOrMergeGenericArgumentValue(ValueHolder newValue) {
247247
* @param requiredType the type to match
248248
* @return the ValueHolder for the argument, or {@code null} if none set
249249
*/
250-
public ValueHolder getGenericArgumentValue(Class requiredType) {
250+
public ValueHolder getGenericArgumentValue(Class<?> requiredType) {
251251
return getGenericArgumentValue(requiredType, null, null);
252252
}
253253

@@ -257,7 +257,7 @@ public ValueHolder getGenericArgumentValue(Class requiredType) {
257257
* @param requiredName the name to match
258258
* @return the ValueHolder for the argument, or {@code null} if none set
259259
*/
260-
public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName) {
260+
public ValueHolder getGenericArgumentValue(Class<?> requiredType, String requiredName) {
261261
return getGenericArgumentValue(requiredType, requiredName, null);
262262
}
263263

@@ -273,7 +273,7 @@ public ValueHolder getGenericArgumentValue(Class requiredType, String requiredNa
273273
* in the current resolution process and should therefore not be returned again
274274
* @return the ValueHolder for the argument, or {@code null} if none found
275275
*/
276-
public ValueHolder getGenericArgumentValue(Class requiredType, String requiredName, Set<ValueHolder> usedValueHolders) {
276+
public ValueHolder getGenericArgumentValue(Class<?> requiredType, String requiredName, Set<ValueHolder> usedValueHolders) {
277277
for (ValueHolder valueHolder : this.genericArgumentValues) {
278278
if (usedValueHolders != null && usedValueHolders.contains(valueHolder)) {
279279
continue;
@@ -309,38 +309,40 @@ public List<ValueHolder> getGenericArgumentValues() {
309309
* Look for an argument value that either corresponds to the given index
310310
* in the constructor argument list or generically matches by type.
311311
* @param index the index in the constructor argument list
312-
* @param requiredType the type to match
312+
* @param requiredType the parameter type to match
313313
* @return the ValueHolder for the argument, or {@code null} if none set
314314
*/
315-
public ValueHolder getArgumentValue(int index, Class requiredType) {
315+
public ValueHolder getArgumentValue(int index, Class<?> requiredType) {
316316
return getArgumentValue(index, requiredType, null, null);
317317
}
318318

319319
/**
320320
* Look for an argument value that either corresponds to the given index
321321
* in the constructor argument list or generically matches by type.
322322
* @param index the index in the constructor argument list
323-
* @param requiredType the type to match
324-
* @param requiredName the name to match
323+
* @param requiredType the parameter type to match
324+
* @param requiredName the parameter name to match
325325
* @return the ValueHolder for the argument, or {@code null} if none set
326326
*/
327-
public ValueHolder getArgumentValue(int index, Class requiredType, String requiredName) {
327+
public ValueHolder getArgumentValue(int index, Class<?> requiredType, String requiredName) {
328328
return getArgumentValue(index, requiredType, requiredName, null);
329329
}
330330

331331
/**
332332
* Look for an argument value that either corresponds to the given index
333333
* in the constructor argument list or generically matches by type.
334334
* @param index the index in the constructor argument list
335-
* @param requiredType the type to match (can be {@code null} to find
336-
* an untyped argument value)
335+
* @param requiredType the parameter type to match (can be {@code null}
336+
* to find an untyped argument value)
337+
* @param requiredName the parameter name to match (can be {@code null}
338+
* to find an unnamed argument value)
337339
* @param usedValueHolders a Set of ValueHolder objects that have already
338340
* been used in the current resolution process and should therefore not
339341
* be returned again (allowing to return the next generic argument match
340342
* in case of multiple generic argument values of the same type)
341343
* @return the ValueHolder for the argument, or {@code null} if none set
342344
*/
343-
public ValueHolder getArgumentValue(int index, Class requiredType, String requiredName, Set<ValueHolder> usedValueHolders) {
345+
public ValueHolder getArgumentValue(int index, Class<?> requiredType, String requiredName, Set<ValueHolder> usedValueHolders) {
344346
Assert.isTrue(index >= 0, "Index must not be negative");
345347
ValueHolder valueHolder = getIndexedArgumentValue(index, requiredType, requiredName);
346348
if (valueHolder == null) {

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

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
import org.springframework.beans.factory.config.BeanDefinition;
6464
import org.springframework.beans.factory.config.BeanPostProcessor;
6565
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
66-
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
66+
import org.springframework.beans.factory.config.ConstructorArgumentValues;
6767
import org.springframework.beans.factory.config.DependencyDescriptor;
6868
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
6969
import org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor;
@@ -632,12 +632,6 @@ protected Class<?> getTypeForFactoryMethod(String beanName, RootBeanDefinition m
632632
return null;
633633
}
634634

635-
List<ValueHolder> argumentValues = mbd.getConstructorArgumentValues().getGenericArgumentValues();
636-
Object[] args = new Object[argumentValues.size()];
637-
for (int i = 0; i < args.length; i++) {
638-
args[i] = argumentValues.get(i).getValue();
639-
}
640-
641635
// If all factory methods have the same return type, return that type.
642636
// Can't clearly figure out exact method due to type converting / autowiring!
643637
int minNrOfArgs = mbd.getConstructorArgumentValues().getArgumentCount();
@@ -647,6 +641,27 @@ protected Class<?> getTypeForFactoryMethod(String beanName, RootBeanDefinition m
647641
if (Modifier.isStatic(factoryMethod.getModifiers()) == isStatic &&
648642
factoryMethod.getName().equals(mbd.getFactoryMethodName()) &&
649643
factoryMethod.getParameterTypes().length >= minNrOfArgs) {
644+
Class<?>[] paramTypes = factoryMethod.getParameterTypes();
645+
String[] paramNames = null;
646+
ParameterNameDiscoverer pnd = getParameterNameDiscoverer();
647+
if (pnd != null) {
648+
paramNames = pnd.getParameterNames(factoryMethod);
649+
}
650+
ConstructorArgumentValues cav = mbd.getConstructorArgumentValues();
651+
Set<ConstructorArgumentValues.ValueHolder> usedValueHolders =
652+
new HashSet<ConstructorArgumentValues.ValueHolder>(paramTypes.length);
653+
Object[] args = new Object[paramTypes.length];
654+
for (int i = 0; i < args.length; i++) {
655+
ConstructorArgumentValues.ValueHolder valueHolder = cav.getArgumentValue(
656+
i, paramTypes[i], (paramNames != null ? paramNames[i] : null), usedValueHolders);
657+
if (valueHolder == null) {
658+
valueHolder = cav.getGenericArgumentValue(null, null, usedValueHolders);
659+
}
660+
if (valueHolder != null) {
661+
args[i] = valueHolder.getValue();
662+
usedValueHolders.add(valueHolder);
663+
}
664+
}
650665
Class<?> returnType = AutowireUtils.resolveReturnTypeForFactoryMethod(
651666
factoryMethod, args, getBeanClassLoader());
652667
if (returnType != null) {

spring-beans/src/test/java/org/springframework/beans/factory/support/BeanFactoryGenericsTests.java

Lines changed: 103 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,9 @@
1616

1717
package org.springframework.beans.factory.support;
1818

19-
import static org.junit.Assert.assertEquals;
20-
import static org.junit.Assert.assertNotSame;
21-
import static org.junit.Assert.assertNull;
22-
import static org.junit.Assert.assertSame;
23-
import static org.junit.Assert.assertTrue;
24-
import static org.junit.Assert.fail;
25-
19+
import java.lang.reflect.InvocationHandler;
20+
import java.lang.reflect.Method;
21+
import java.lang.reflect.Proxy;
2622
import java.net.MalformedURLException;
2723
import java.net.URI;
2824
import java.net.URL;
@@ -37,6 +33,7 @@
3733

3834
import org.junit.Test;
3935
import org.mockito.Mockito;
36+
4037
import org.springframework.beans.PropertyEditorRegistrar;
4138
import org.springframework.beans.PropertyEditorRegistry;
4239
import org.springframework.beans.factory.BeanCreationException;
@@ -46,12 +43,12 @@
4643
import org.springframework.core.io.UrlResource;
4744
import org.springframework.tests.Assume;
4845
import org.springframework.tests.TestGroup;
49-
5046
import org.springframework.tests.sample.beans.GenericBean;
5147
import org.springframework.tests.sample.beans.GenericIntegerBean;
5248
import org.springframework.tests.sample.beans.GenericSetOfIntegerBean;
5349
import org.springframework.tests.sample.beans.TestBean;
5450

51+
import static org.junit.Assert.*;
5552

5653
/**
5754
* @author Juergen Hoeller
@@ -115,7 +112,7 @@ public void testGenericListPropertyWithInvalidElementType() {
115112
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
116113
RootBeanDefinition rbd = new RootBeanDefinition(GenericIntegerBean.class);
117114

118-
List input = new ArrayList();
115+
List<Integer> input = new ArrayList<Integer>();
119116
input.add(1);
120117
rbd.getPropertyValues().add("testBeanList", input);
121118

@@ -655,18 +652,22 @@ public void testSetBean() throws Exception {
655652
}
656653

657654
/**
658-
* Tests support for parameterized {@code factory-method} declarations such
659-
* as Mockito {@code mock()} method which has the following signature.
660-
*
661-
* <pre>{@code
655+
* Tests support for parameterized static {@code factory-method} declarations such as
656+
* Mockito's {@code mock()} method which has the following signature.
657+
*
658+
* <pre>
659+
* {@code
662660
* public static <T> T mock(Class<T> classToMock)
663-
* }</pre>
664-
*
661+
* }
662+
* </pre>
663+
*
664+
* <p>
665665
* See SPR-9493
666+
*
666667
* @since 3.2
667668
*/
668669
@Test
669-
public void parameterizedFactoryMethod() {
670+
public void parameterizedStaticFactoryMethod() {
670671
RootBeanDefinition rbd = new RootBeanDefinition(Mockito.class);
671672
rbd.setFactoryMethodName("mock");
672673
rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class);
@@ -678,6 +679,71 @@ public void parameterizedFactoryMethod() {
678679
assertEquals(1, beans.size());
679680
}
680681

682+
/**
683+
* Tests support for parameterized instance {@code factory-method} declarations such
684+
* as EasyMock's {@code IMocksControl.createMock()} method which has the following
685+
* signature.
686+
* <pre>
687+
* {@code
688+
* public <T> T createMock(Class<T> toMock)
689+
* }
690+
* </pre>
691+
* <p>See SPR-10411
692+
*/
693+
@Test
694+
public void parameterizedInstanceFactoryMethod() {
695+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
696+
697+
RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class);
698+
bf.registerBeanDefinition("mocksControl", rbd);
699+
700+
rbd = new RootBeanDefinition();
701+
rbd.setFactoryBeanName("mocksControl");
702+
rbd.setFactoryMethodName("createMock");
703+
rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class);
704+
705+
bf.registerBeanDefinition("mock", rbd);
706+
707+
Map<String, Runnable> beans = bf.getBeansOfType(Runnable.class);
708+
assertEquals(1, beans.size());
709+
}
710+
711+
@Test
712+
public void parameterizedInstanceFactoryMethodWithNonResolvedClassName() {
713+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
714+
715+
RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class);
716+
bf.registerBeanDefinition("mocksControl", rbd);
717+
718+
rbd = new RootBeanDefinition();
719+
rbd.setFactoryBeanName("mocksControl");
720+
rbd.setFactoryMethodName("createMock");
721+
rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class.getName());
722+
723+
bf.registerBeanDefinition("mock", rbd);
724+
725+
Map<String, Runnable> beans = bf.getBeansOfType(Runnable.class);
726+
assertEquals(1, beans.size());
727+
}
728+
729+
@Test
730+
public void parameterizedInstanceFactoryMethodWithIndexedArgument() {
731+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
732+
733+
RootBeanDefinition rbd = new RootBeanDefinition(MocksControl.class);
734+
bf.registerBeanDefinition("mocksControl", rbd);
735+
736+
rbd = new RootBeanDefinition();
737+
rbd.setFactoryBeanName("mocksControl");
738+
rbd.setFactoryMethodName("createMock");
739+
rbd.getConstructorArgumentValues().addIndexedArgumentValue(0, Runnable.class);
740+
741+
bf.registerBeanDefinition("mock", rbd);
742+
743+
Map<String, Runnable> beans = bf.getBeansOfType(Runnable.class);
744+
assertEquals(1, beans.size());
745+
}
746+
681747

682748
@SuppressWarnings("serial")
683749
public static class NamedUrlList extends LinkedList<URL> {
@@ -722,4 +788,25 @@ public void setUrlNames(Set<URI> urlNames) throws MalformedURLException {
722788
}
723789
}
724790

791+
/**
792+
* Pseudo-implementation of EasyMock's {@code MocksControl} class.
793+
*/
794+
public static class MocksControl {
795+
796+
@SuppressWarnings("unchecked")
797+
public <T> T createMock(Class<T> toMock) {
798+
799+
return (T) Proxy.newProxyInstance(
800+
BeanFactoryGenericsTests.class.getClassLoader(),
801+
new Class[] { toMock }, new InvocationHandler() {
802+
803+
@Override
804+
public Object invoke(Object proxy, Method method, Object[] args)
805+
throws Throwable {
806+
throw new UnsupportedOperationException("mocked!");
807+
}
808+
});
809+
}
810+
}
811+
725812
}

0 commit comments

Comments
 (0)