Skip to content

Commit 44f79f9

Browse files
committed
Fixed resolveReturnTypeForFactoryMethod to unwrap TypedStringValue
XML-defined arguments values are initially turned into TypedStringValue wrappers. If we encounter an unresolved argument, we need to unwrap such a TypedStringValue and then try to treat its content as a class name. Issue: SPR-11034
1 parent 3eaec64 commit 44f79f9

File tree

2 files changed

+62
-17
lines changed

2 files changed

+62
-17
lines changed

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

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@
3131
import java.util.Comparator;
3232
import java.util.Set;
3333

34+
import org.springframework.beans.BeanMetadataElement;
3435
import org.springframework.beans.factory.ObjectFactory;
36+
import org.springframework.beans.factory.config.TypedStringValue;
3537
import org.springframework.util.Assert;
3638
import org.springframework.util.ClassUtils;
3739

@@ -192,8 +194,8 @@ public static Class<?> resolveReturnTypeForFactoryMethod(Method method, Object[]
192194

193195
TypeVariable<Method>[] declaredTypeVariables = method.getTypeParameters();
194196
Type genericReturnType = method.getGenericReturnType();
195-
Type[] methodArgumentTypes = method.getGenericParameterTypes();
196-
Assert.isTrue(args.length == methodArgumentTypes.length, "Argument array does not match parameter count");
197+
Type[] methodParameterTypes = method.getGenericParameterTypes();
198+
Assert.isTrue(args.length == methodParameterTypes.length, "Argument array does not match parameter count");
197199

198200
// Ensure that the type variable (e.g., T) is declared directly on the method
199201
// itself (e.g., via <T>), not on the enclosing class or interface.
@@ -206,30 +208,57 @@ public static Class<?> resolveReturnTypeForFactoryMethod(Method method, Object[]
206208
}
207209

208210
if (locallyDeclaredTypeVariableMatchesReturnType) {
209-
for (int i = 0; i < methodArgumentTypes.length; i++) {
210-
Type currentMethodArgumentType = methodArgumentTypes[i];
211-
if (currentMethodArgumentType.equals(genericReturnType)) {
212-
return args[i].getClass();
211+
for (int i = 0; i < methodParameterTypes.length; i++) {
212+
Type methodParameterType = methodParameterTypes[i];
213+
Object arg = args[i];
214+
if (methodParameterType.equals(genericReturnType)) {
215+
if (arg instanceof TypedStringValue) {
216+
TypedStringValue typedValue = ((TypedStringValue) arg);
217+
if (typedValue.hasTargetType()) {
218+
return typedValue.getTargetType();
219+
}
220+
try {
221+
return typedValue.resolveTargetType(classLoader);
222+
}
223+
catch (ClassNotFoundException ex) {
224+
throw new IllegalStateException("Failed to resolve typed value", ex);
225+
}
226+
}
227+
// Only consider argument type if it is a simple value...
228+
if (arg != null && !(arg instanceof BeanMetadataElement)) {
229+
return arg.getClass();
230+
}
231+
return method.getReturnType();
213232
}
214-
if (currentMethodArgumentType instanceof ParameterizedType) {
215-
ParameterizedType parameterizedType = (ParameterizedType) currentMethodArgumentType;
233+
else if (methodParameterType instanceof ParameterizedType) {
234+
ParameterizedType parameterizedType = (ParameterizedType) methodParameterType;
216235
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
217236
for (Type typeArg : actualTypeArguments) {
218237
if (typeArg.equals(genericReturnType)) {
219-
Object arg = args[i];
220238
if (arg instanceof Class) {
221239
return (Class<?>) arg;
222240
}
223-
else if (arg instanceof String) {
224-
try {
225-
return classLoader.loadClass((String) arg);
241+
else {
242+
String className = null;
243+
if (arg instanceof String) {
244+
className = (String) arg;
226245
}
227-
catch (ClassNotFoundException ex) {
228-
throw new IllegalStateException(
229-
"Could not resolve specified class name argument [" + arg + "]", ex);
246+
else if (arg instanceof TypedStringValue) {
247+
TypedStringValue typedValue = ((TypedStringValue) arg);
248+
String targetTypeName = typedValue.getTargetTypeName();
249+
if (targetTypeName == null || Class.class.getName().equals(targetTypeName)) {
250+
className = typedValue.getValue();
251+
}
252+
}
253+
if (className != null) {
254+
try {
255+
return classLoader.loadClass(className);
256+
}
257+
catch (ClassNotFoundException ex) {
258+
throw new IllegalStateException(
259+
"Could not resolve specified class name argument [" + arg + "]", ex);
260+
}
230261
}
231-
}
232-
else {
233262
// Consider adding logic to determine the class of the typeArg, if possible.
234263
// For now, just fall back...
235264
return method.getReturnType();

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.beans.PropertyEditorRegistrar;
3838
import org.springframework.beans.PropertyEditorRegistry;
3939
import org.springframework.beans.factory.BeanCreationException;
40+
import org.springframework.beans.factory.config.TypedStringValue;
4041
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
4142
import org.springframework.beans.propertyeditors.CustomNumberEditor;
4243
import org.springframework.core.io.ClassPathResource;
@@ -720,6 +721,21 @@ public void parameterizedInstanceFactoryMethodWithNonResolvedClassName() {
720721
assertEquals(1, beans.size());
721722
}
722723

724+
@Test
725+
public void parameterizedInstanceFactoryMethodWithWrappedClassName() {
726+
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
727+
728+
RootBeanDefinition rbd = new RootBeanDefinition();
729+
rbd.setBeanClassName(Mockito.class.getName());
730+
rbd.setFactoryMethodName("mock");
731+
// TypedStringValue used to be equivalent to an XML-defined argument String
732+
rbd.getConstructorArgumentValues().addGenericArgumentValue(new TypedStringValue(Runnable.class.getName()));
733+
bf.registerBeanDefinition("mock", rbd);
734+
735+
Map<String, Runnable> beans = bf.getBeansOfType(Runnable.class);
736+
assertEquals(1, beans.size());
737+
}
738+
723739
@Test
724740
public void parameterizedInstanceFactoryMethodWithIndexedArgument() {
725741
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();

0 commit comments

Comments
 (0)