Skip to content

Commit b00c31a

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 (cherry picked from commit b093b84)
1 parent 23cc44f commit b00c31a

File tree

4 files changed

+109
-42
lines changed

4 files changed

+109
-42
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 ->
@@ -341,7 +341,7 @@ public BeanWrapper instantiateUsingFactoryMethod(final String beanName, final Ro
341341
this.beanFactory.initBeanWrapper(bw);
342342

343343
Object factoryBean;
344-
Class factoryClass;
344+
Class<?> factoryClass;
345345
boolean isStatic;
346346

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

402-
final Class factoryClazz = factoryClass;
402+
final Class<?> factoryClazz = factoryClass;
403403
if (System.getSecurityManager() != null) {
404404
rawCandidates = AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
405405
public Method[] run() {
@@ -445,7 +445,7 @@ public Method[] run() {
445445

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

450450
if (paramTypes.length >= minNrOfArgs) {
451451
ArgumentsHolder argsHolder;
@@ -503,7 +503,15 @@ public Method[] run() {
503503
minTypeDiffWeight = typeDiffWeight;
504504
ambiguousFactoryMethods = null;
505505
}
506-
else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight) {
506+
// Find out about ambiguity: In case of the same type difference weight
507+
// for methods with the same number of parameters, collect such candidates
508+
// and eventually raise an ambiguity exception.
509+
// However, only perform that check in non-lenient constructor resolution mode,
510+
// and explicitly ignore overridden methods (with the same parameter signature).
511+
else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight &&
512+
!mbd.isLenientConstructorResolution() &&
513+
paramTypes.length == factoryMethodToUse.getParameterTypes().length &&
514+
!Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) {
507515
if (ambiguousFactoryMethods == null) {
508516
ambiguousFactoryMethods = new LinkedHashSet<Method>();
509517
ambiguousFactoryMethods.add(factoryMethodToUse);
@@ -540,7 +548,7 @@ else if (void.class.equals(factoryMethodToUse.getReturnType())) {
540548
"Invalid factory method '" + mbd.getFactoryMethodName() +
541549
"': needs to have a non-void return type!");
542550
}
543-
else if (ambiguousFactoryMethods != null && !mbd.isLenientConstructorResolution()) {
551+
else if (ambiguousFactoryMethods != null) {
544552
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
545553
"Ambiguous factory method matches found in bean '" + beanName + "' " +
546554
"(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " +
@@ -644,7 +652,7 @@ private int resolveConstructorArguments(
644652
*/
645653
private ArgumentsHolder createArgumentArray(
646654
String beanName, RootBeanDefinition mbd, ConstructorArgumentValues resolvedValues,
647-
BeanWrapper bw, Class[] paramTypes, String[] paramNames, Object methodOrCtor,
655+
BeanWrapper bw, Class<?>[] paramTypes, String[] paramNames, Object methodOrCtor,
648656
boolean autowiring) throws UnsatisfiedDependencyException {
649657

650658
String methodType = (methodOrCtor instanceof Constructor ? "constructor" : "factory method");
@@ -750,7 +758,7 @@ private ArgumentsHolder createArgumentArray(
750758
private Object[] resolvePreparedArguments(
751759
String beanName, RootBeanDefinition mbd, BeanWrapper bw, Member methodOrCtor, Object[] argsToResolve) {
752760

753-
Class[] paramTypes = (methodOrCtor instanceof Method ?
761+
Class<?>[] paramTypes = (methodOrCtor instanceof Method ?
754762
((Method) methodOrCtor).getParameterTypes() : ((Constructor) methodOrCtor).getParameterTypes());
755763
TypeConverter converter = (this.beanFactory.getCustomTypeConverter() != null ?
756764
this.beanFactory.getCustomTypeConverter() : bw);
@@ -822,7 +830,7 @@ public ArgumentsHolder(Object[] args) {
822830
this.preparedArguments = args;
823831
}
824832

825-
public int getTypeDifferenceWeight(Class[] paramTypes) {
833+
public int getTypeDifferenceWeight(Class<?>[] paramTypes) {
826834
// If valid arguments found, determine type difference weight.
827835
// Try type difference weight on both the converted arguments and
828836
// the raw arguments. If the raw weight is better, use it.
@@ -832,7 +840,7 @@ public int getTypeDifferenceWeight(Class[] paramTypes) {
832840
return (rawTypeDiffWeight < typeDiffWeight ? rawTypeDiffWeight : typeDiffWeight);
833841
}
834842

835-
public int getAssignabilityWeight(Class[] paramTypes) {
843+
public int getAssignabilityWeight(Class<?>[] paramTypes) {
836844
for (int i = 0; i < paramTypes.length; i++) {
837845
if (!ClassUtils.isAssignableValue(paramTypes[i], this.arguments[i])) {
838846
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
@@ -310,6 +310,7 @@ private static class ConfigurationClassBeanDefinition extends RootBeanDefinition
310310

311311
public ConfigurationClassBeanDefinition(ConfigurationClass configClass) {
312312
this.annotationMetadata = configClass.getMetadata();
313+
setLenientConstructorResolution(false);
313314
}
314315

315316
public ConfigurationClassBeanDefinition(RootBeanDefinition original, ConfigurationClass configClass) {

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

Lines changed: 76 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,20 @@
1616

1717
package org.springframework.context.annotation;
1818

19-
import static org.hamcrest.CoreMatchers.equalTo;
20-
import static org.junit.Assert.assertThat;
21-
import static org.junit.Assert.assertTrue;
22-
import static org.junit.Assert.fail;
23-
2419
import java.lang.annotation.Inherited;
20+
import java.util.List;
2521

22+
import org.junit.Rule;
2623
import org.junit.Test;
24+
import org.junit.rules.ExpectedException;
25+
2726
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
2827
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
2928
import org.springframework.beans.factory.support.RootBeanDefinition;
3029

30+
import static org.hamcrest.CoreMatchers.*;
31+
import static org.junit.Assert.*;
32+
3133
/**
3234
* Tests regarding overloading and overriding of bean methods.
3335
* Related to SPR-6618.
@@ -42,35 +44,40 @@
4244
* the most specific subclass bean method will always be the one that is invoked.
4345
*
4446
* @author Chris Beams
47+
* @author Phillip Webb
4548
*/
49+
@SuppressWarnings("resource")
4650
public class BeanMethodPolymorphismTests {
4751

52+
@Rule
53+
public ExpectedException thrown = ExpectedException.none();
54+
55+
4856
@Test
4957
public void beanMethodOverloadingWithoutInheritance() {
58+
5059
@SuppressWarnings({ "hiding" })
5160
@Configuration class Config {
5261
@Bean String aString() { return "na"; }
5362
@Bean String aString(Integer dependency) { return "na"; }
5463
}
55-
try {
56-
new AnnotationConfigApplicationContext(Config.class);
57-
fail("expected bean method overloading exception");
58-
} catch (BeanDefinitionParsingException ex) {
59-
assertTrue(ex.getMessage(), ex.getMessage().contains("2 overloaded @Bean methods named 'aString'"));
60-
}
64+
65+
this.thrown.expect(BeanDefinitionParsingException.class);
66+
this.thrown.expectMessage("overloaded @Bean methods named 'aString'");
67+
new AnnotationConfigApplicationContext(Config.class);
6168
}
6269

6370
@Test
6471
public void beanMethodOverloadingWithInheritance() {
6572
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SubConfig.class);
6673
assertThat(ctx.getBean(String.class), equalTo("overloaded5"));
6774
}
68-
static @Configuration class SuperConfig {
69-
@Bean String aString() { return "super"; }
70-
}
71-
static @Configuration class SubConfig {
72-
@Bean Integer anInt() { return 5; }
73-
@Bean String aString(Integer dependency) { return "overloaded"+dependency; }
75+
76+
@Test
77+
public void beanMethodOverloadingWithInheritanceAndList() {
78+
// SPR-11025
79+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SubConfigWithList.class);
80+
assertThat(ctx.getBean(String.class), equalTo("overloaded5"));
7481
}
7582

7683
/**
@@ -83,10 +90,6 @@ public void beanMethodShadowing() {
8390
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ShadowConfig.class);
8491
assertThat(ctx.getBean(String.class), equalTo("shadow"));
8592
}
86-
@Import(SubConfig.class)
87-
static @Configuration class ShadowConfig {
88-
@Bean String aString() { return "shadow"; }
89-
}
9093

9194
/**
9295
* Tests that polymorphic Configuration classes need not explicitly redeclare the
@@ -110,11 +113,63 @@ static class BaseConfig {
110113
public TestBean testBean() {
111114
return new TestBean();
112115
}
116+
113117
}
114118

115119

116120
@Configuration
117121
static class Config extends BaseConfig {
118122
}
119123

124+
125+
@Configuration
126+
static class SuperConfig {
127+
128+
@Bean
129+
String aString() {
130+
return "super";
131+
}
132+
}
133+
134+
135+
@Configuration
136+
static class SubConfig extends SuperConfig {
137+
138+
@Bean
139+
Integer anInt() {
140+
return 5;
141+
}
142+
143+
@Bean
144+
String aString(Integer dependency) {
145+
return "overloaded" + dependency;
146+
}
147+
}
148+
149+
150+
@Configuration
151+
static class SubConfigWithList extends SuperConfig {
152+
153+
@Bean
154+
Integer anInt() {
155+
return 5;
156+
}
157+
158+
@Bean
159+
String aString(List<Integer> dependency) {
160+
return "overloaded" + dependency.get(0);
161+
}
162+
}
163+
164+
165+
@Configuration
166+
@Import(SubConfig.class)
167+
static class ShadowConfig {
168+
169+
@Bean
170+
String aString() {
171+
return "shadow";
172+
}
173+
}
174+
120175
}

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)