Skip to content

Commit b093b84

Browse files
committed
Use non-lenient constructor resolution mode for @bean methods
Since @bean methods are never used with externally specified constructor argument values but rather just with autowiring, the non-lenient constructor resolution mode is appropriate in case of an overloaded @bean method, not performing any type difference weight checks. This change includes a refinement of Spring's existing non-lenient constructor resolution (which needs to be explicitly turned on and is therefore not well tested), narrowing the conditions for the ambiguity check (only in case of the same number of arguments and not for overridden methods). Issue: SPR-10988
1 parent 49758a2 commit b093b84

File tree

4 files changed

+34
-23
lines changed

4 files changed

+34
-23
lines changed

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

Lines changed: 20 additions & 12 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.
@@ -151,7 +151,7 @@ public BeanWrapper autowireConstructor(
151151
// Take specified constructors, if any.
152152
Constructor[] candidates = chosenCtors;
153153
if (candidates == null) {
154-
Class beanClass = mbd.getBeanClass();
154+
Class<?> beanClass = mbd.getBeanClass();
155155
try {
156156
candidates = (mbd.isNonPublicAccessAllowed() ?
157157
beanClass.getDeclaredConstructors() : beanClass.getConstructors());
@@ -169,7 +169,7 @@ public BeanWrapper autowireConstructor(
169169

170170
for (int i = 0; i < candidates.length; i++) {
171171
Constructor<?> candidate = candidates[i];
172-
Class[] paramTypes = candidate.getParameterTypes();
172+
Class<?>[] paramTypes = candidate.getParameterTypes();
173173

174174
if (constructorToUse != null && argsToUse.length > paramTypes.length) {
175175
// Already found greedy constructor that can be satisfied ->
@@ -342,7 +342,7 @@ public BeanWrapper instantiateUsingFactoryMethod(final String beanName, final Ro
342342
this.beanFactory.initBeanWrapper(bw);
343343

344344
Object factoryBean;
345-
Class factoryClass;
345+
Class<?> factoryClass;
346346
boolean isStatic;
347347

348348
String factoryBeanName = mbd.getFactoryBeanName();
@@ -400,7 +400,7 @@ public BeanWrapper instantiateUsingFactoryMethod(final String beanName, final Ro
400400
factoryClass = ClassUtils.getUserClass(factoryClass);
401401
Method[] rawCandidates;
402402

403-
final Class factoryClazz = factoryClass;
403+
final Class<?> factoryClazz = factoryClass;
404404
if (System.getSecurityManager() != null) {
405405
rawCandidates = AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
406406
@Override
@@ -447,7 +447,7 @@ public Method[] run() {
447447

448448
for (int i = 0; i < candidates.length; i++) {
449449
Method candidate = candidates[i];
450-
Class[] paramTypes = candidate.getParameterTypes();
450+
Class<?>[] paramTypes = candidate.getParameterTypes();
451451

452452
if (paramTypes.length >= minNrOfArgs) {
453453
ArgumentsHolder argsHolder;
@@ -505,7 +505,15 @@ public Method[] run() {
505505
minTypeDiffWeight = typeDiffWeight;
506506
ambiguousFactoryMethods = null;
507507
}
508-
else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight) {
508+
// Find out about ambiguity: In case of the same type difference weight
509+
// for methods with the same number of parameters, collect such candidates
510+
// and eventually raise an ambiguity exception.
511+
// However, only perform that check in non-lenient constructor resolution mode,
512+
// and explicitly ignore overridden methods (with the same parameter signature).
513+
else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight &&
514+
!mbd.isLenientConstructorResolution() &&
515+
paramTypes.length == factoryMethodToUse.getParameterTypes().length &&
516+
!Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) {
509517
if (ambiguousFactoryMethods == null) {
510518
ambiguousFactoryMethods = new LinkedHashSet<Method>();
511519
ambiguousFactoryMethods.add(factoryMethodToUse);
@@ -542,7 +550,7 @@ else if (void.class.equals(factoryMethodToUse.getReturnType())) {
542550
"Invalid factory method '" + mbd.getFactoryMethodName() +
543551
"': needs to have a non-void return type!");
544552
}
545-
else if (ambiguousFactoryMethods != null && !mbd.isLenientConstructorResolution()) {
553+
else if (ambiguousFactoryMethods != null) {
546554
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
547555
"Ambiguous factory method matches found in bean '" + beanName + "' " +
548556
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
@@ -647,7 +655,7 @@ private int resolveConstructorArguments(
647655
*/
648656
private ArgumentsHolder createArgumentArray(
649657
String beanName, RootBeanDefinition mbd, ConstructorArgumentValues resolvedValues,
650-
BeanWrapper bw, Class[] paramTypes, String[] paramNames, Object methodOrCtor,
658+
BeanWrapper bw, Class<?>[] paramTypes, String[] paramNames, Object methodOrCtor,
651659
boolean autowiring) throws UnsatisfiedDependencyException {
652660

653661
String methodType = (methodOrCtor instanceof Constructor ? "constructor" : "factory method");
@@ -753,7 +761,7 @@ private ArgumentsHolder createArgumentArray(
753761
private Object[] resolvePreparedArguments(
754762
String beanName, RootBeanDefinition mbd, BeanWrapper bw, Member methodOrCtor, Object[] argsToResolve) {
755763

756-
Class[] paramTypes = (methodOrCtor instanceof Method ?
764+
Class<?>[] paramTypes = (methodOrCtor instanceof Method ?
757765
((Method) methodOrCtor).getParameterTypes() : ((Constructor) methodOrCtor).getParameterTypes());
758766
TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ?
759767
this.beanFactory.getCustomTypeConverter() : bw);
@@ -825,7 +833,7 @@ public ArgumentsHolder(Object[] args) {
825833
this.preparedArguments = args;
826834
}
827835

828-
public int getTypeDifferenceWeight(Class[] paramTypes) {
836+
public int getTypeDifferenceWeight(Class<?>[] paramTypes) {
829837
// If valid arguments found, determine type difference weight.
830838
// Try type difference weight on both the converted arguments and
831839
// the raw arguments. If the raw weight is better, use it.
@@ -835,7 +843,7 @@ public int getTypeDifferenceWeight(Class[] paramTypes) {
835843
return (rawTypeDiffWeight < typeDiffWeight ? rawTypeDiffWeight : typeDiffWeight);
836844
}
837845

838-
public int getAssignabilityWeight(Class[] paramTypes) {
846+
public int getAssignabilityWeight(Class<?>[] paramTypes) {
839847
for (int i = 0; i < paramTypes.length; i++) {
840848
if (!ClassUtils.isAssignableValue(paramTypes[i], this.arguments[i])) {
841849
return Integer.MAX_VALUE;

spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ private static class ConfigurationClassBeanDefinition extends RootBeanDefinition
332332

333333
public ConfigurationClassBeanDefinition(ConfigurationClass configClass) {
334334
this.annotationMetadata = configClass.getMetadata();
335+
setLenientConstructorResolution(false);
335336
}
336337

337338
public ConfigurationClassBeanDefinition(RootBeanDefinition original, ConfigurationClass configClass) {

spring-context/src/test/java/org/springframework/context/annotation/BeanMethodPolymorphismTests.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
import java.lang.annotation.Inherited;
2020
import java.util.List;
2121

22-
import org.junit.Ignore;
2322
import org.junit.Rule;
2423
import org.junit.Test;
2524
import org.junit.rules.ExpectedException;
25+
2626
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
2727
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
2828
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -74,7 +74,6 @@ public void beanMethodOverloadingWithInheritance() {
7474
}
7575

7676
@Test
77-
@Ignore
7877
public void beanMethodOverloadingWithInheritanceAndList() {
7978
// SPR-11025
8079
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SubConfigWithList.class);

spring-core/src/main/java/org/springframework/util/MethodInvoker.java

Lines changed: 12 additions & 9 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.
@@ -40,7 +40,7 @@
4040
*/
4141
public class MethodInvoker {
4242

43-
private Class targetClass;
43+
private Class<?> targetClass;
4444

4545
private Object targetObject;
4646

@@ -61,14 +61,14 @@ public class MethodInvoker {
6161
* @see #setTargetObject
6262
* @see #setTargetMethod
6363
*/
64-
public void setTargetClass(Class targetClass) {
64+
public void setTargetClass(Class<?> targetClass) {
6565
this.targetClass = targetClass;
6666
}
6767

6868
/**
6969
* Return the target class on which to call the target method.
7070
*/
71-
public Class getTargetClass() {
71+
public Class<?> getTargetClass() {
7272
return this.targetClass;
7373
}
7474

@@ -158,7 +158,7 @@ public void prepare() throws ClassNotFoundException, NoSuchMethodException {
158158
this.targetMethod = methodName;
159159
}
160160

161-
Class targetClass = getTargetClass();
161+
Class<?> targetClass = getTargetClass();
162162
String targetMethod = getTargetMethod();
163163
if (targetClass == null) {
164164
throw new IllegalArgumentException("Either 'targetClass' or 'targetObject' is required");
@@ -194,7 +194,7 @@ public void prepare() throws ClassNotFoundException, NoSuchMethodException {
194194
* @return the resolved Class
195195
* @throws ClassNotFoundException if the class name was invalid
196196
*/
197-
protected Class resolveClassName(String className) throws ClassNotFoundException {
197+
protected Class<?> resolveClassName(String className) throws ClassNotFoundException {
198198
return ClassUtils.forName(className, ClassUtils.getDefaultClassLoader());
199199
}
200200

@@ -287,19 +287,22 @@ public Object invoke() throws InvocationTargetException, IllegalAccessException
287287
* Therefore, with an arg of type Integer, a constructor (Integer) would be preferred to a
288288
* constructor (Number) which would in turn be preferred to a constructor (Object).
289289
* All argument weights get accumulated.
290+
* <p>Note: This is the algorithm used by MethodInvoker itself and also the algorithm
291+
* used for constructor and factory method selection in Spring's bean container (in case
292+
* of lenient constructor resolution which is the default for regular bean definitions).
290293
* @param paramTypes the parameter types to match
291294
* @param args the arguments to match
292295
* @return the accumulated weight for all arguments
293296
*/
294-
public static int getTypeDifferenceWeight(Class[] paramTypes, Object[] args) {
297+
public static int getTypeDifferenceWeight(Class<?>[] paramTypes, Object[] args) {
295298
int result = 0;
296299
for (int i = 0; i < paramTypes.length; i++) {
297300
if (!ClassUtils.isAssignableValue(paramTypes[i], args[i])) {
298301
return Integer.MAX_VALUE;
299302
}
300303
if (args[i] != null) {
301-
Class paramType = paramTypes[i];
302-
Class superClass = args[i].getClass().getSuperclass();
304+
Class<?> paramType = paramTypes[i];
305+
Class<?> superClass = args[i].getClass().getSuperclass();
303306
while (superClass != null) {
304307
if (paramType.equals(superClass)) {
305308
result = result + 2;

0 commit comments

Comments
 (0)