Skip to content

Commit 935bd25

Browse files
committed
Revised @bean processing rules
@bean method metadata is always being picked from the most concrete subclass; @bean method overloads are allowed within the same config class as well; and @bean overrides and overloads work with 'allowBeanDefinitionOverriding'=false now. Issue: SPR-10992 Issue: SPR-11025
1 parent e146e53 commit 935bd25

File tree

3 files changed

+92
-73
lines changed

3 files changed

+92
-73
lines changed

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

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.context.annotation;
1818

1919
import java.util.Collections;
20-
import java.util.HashMap;
2120
import java.util.LinkedHashMap;
2221
import java.util.LinkedHashSet;
2322
import java.util.Map;
@@ -156,8 +155,8 @@ public boolean isImported() {
156155
}
157156

158157
/**
159-
* Returns the configuration class that imported this class or {@code null} if
160-
* this configuration was not imported.
158+
* Return the configuration class that imported this class,
159+
* or {@code null} if this configuration was not imported.
161160
* @since 4.0
162161
* @see #isImported()
163162
*/
@@ -182,7 +181,7 @@ public void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar regis
182181
}
183182

184183
public Set<ImportBeanDefinitionRegistrar> getImportBeanDefinitionRegistrars() {
185-
return Collections.unmodifiableSet(importBeanDefinitionRegistrars);
184+
return Collections.unmodifiableSet(this.importBeanDefinitionRegistrars);
186185
}
187186

188187
public Map<String, Class<? extends BeanDefinitionReader>> getImportedResources() {
@@ -197,23 +196,6 @@ public void validate(ProblemReporter problemReporter) {
197196
}
198197
}
199198

200-
// An @Bean method may only be overloaded through inheritance. No single
201-
// @Configuration class may declare two @Bean methods with the same name.
202-
Map<String, Integer> methodNameCounts = new HashMap<String, Integer>();
203-
for (BeanMethod beanMethod : this.beanMethods) {
204-
String fqMethodName = beanMethod.getFullyQualifiedMethodName();
205-
Integer currentCount = methodNameCounts.get(fqMethodName);
206-
int newCount = (currentCount != null ? currentCount + 1 : 1);
207-
methodNameCounts.put(fqMethodName, newCount);
208-
}
209-
for (String fqMethodName : methodNameCounts.keySet()) {
210-
int count = methodNameCounts.get(fqMethodName);
211-
if (count > 1) {
212-
String shortMethodName = ConfigurationMethod.getShortMethodName(fqMethodName);
213-
problemReporter.error(new BeanMethodOverloadingProblem(shortMethodName, count));
214-
}
215-
}
216-
217199
for (BeanMethod beanMethod : this.beanMethods) {
218200
beanMethod.validate(problemReporter);
219201
}
@@ -232,7 +214,7 @@ public int hashCode() {
232214

233215
@Override
234216
public String toString() {
235-
return String.format("[ConfigurationClass:beanName=%s,resource=%s]", this.beanName, this.resource);
217+
return "ConfigurationClass:beanName=" + this.beanName + ",resource=" + this.resource;
236218
}
237219

238220

@@ -247,17 +229,4 @@ public FinalConfigurationProblem() {
247229
}
248230
}
249231

250-
251-
/**
252-
* Bean methods on configuration classes may only be overloaded through inheritance.
253-
*/
254-
private class BeanMethodOverloadingProblem extends Problem {
255-
256-
public BeanMethodOverloadingProblem(String methodName, int count) {
257-
super(String.format("@Configuration class '%s' has %s overloaded @Bean methods named '%s'. " +
258-
"Only one @Bean method of a given name is allowed within each @Configuration class.",
259-
getSimpleName(), count, methodName), new Location(getResource(), getMetadata()));
260-
}
261-
}
262-
263232
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
174174
if (this.conditionEvaluator.shouldSkip(beanMethod.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
175175
return;
176176
}
177+
177178
ConfigurationClass configClass = beanMethod.getConfigurationClass();
178179
MethodMetadata metadata = beanMethod.getMetadata();
179180

@@ -258,9 +259,12 @@ protected boolean isOverriddenByExistingDefinition(BeanMethod beanMethod, String
258259
BeanDefinition existingBeanDef = this.registry.getBeanDefinition(beanName);
259260

260261
// Is the existing bean definition one that was created from a configuration class?
261-
// -> allow the current bean method to override, since both are at second-pass level
262+
// -> allow the current bean method to override, since both are at second-pass level.
263+
// However, if the bean method is an overloaded case on the same configuration class,
264+
// preserve the existing bean definition.
262265
if (existingBeanDef instanceof ConfigurationClassBeanDefinition) {
263-
return false;
266+
ConfigurationClassBeanDefinition ccbd = (ConfigurationClassBeanDefinition) existingBeanDef;
267+
return (ccbd.getMetadata().getClassName().equals(beanMethod.getConfigurationClass().getMetadata().getClassName()));
264268
}
265269

266270
// Has the existing bean definition bean marked as a framework-generated bean?

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

Lines changed: 82 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,10 @@
1616

1717
package org.springframework.context.annotation;
1818

19-
import java.lang.annotation.Inherited;
2019
import java.util.List;
2120

22-
import org.junit.Rule;
2321
import org.junit.Test;
24-
import org.junit.rules.ExpectedException;
2522

26-
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
2723
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
2824
import org.springframework.beans.factory.support.RootBeanDefinition;
2925

@@ -34,50 +30,75 @@
3430
* Tests regarding overloading and overriding of bean methods.
3531
* Related to SPR-6618.
3632
*
37-
* Bean-annotated methods should be able to be overridden, just as any regular
38-
* method. This is straightforward.
39-
*
40-
* Bean-annotated methods should be able to be overloaded, though supporting this
41-
* is more subtle. Essentially, it must be unambiguous to the container which bean
42-
* method to call. A simple way to think about this is that no one Configuration
43-
* class may declare two bean methods with the same name. In the case of inheritance,
44-
* the most specific subclass bean method will always be the one that is invoked.
45-
*
4633
* @author Chris Beams
4734
* @author Phillip Webb
35+
* @author Juergen Hoeller
4836
*/
49-
@SuppressWarnings("resource")
5037
public class BeanMethodPolymorphismTests {
5138

52-
@Rule
53-
public ExpectedException thrown = ExpectedException.none();
54-
55-
5639
@Test
5740
public void beanMethodOverloadingWithoutInheritance() {
41+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
42+
ctx.register(ConfigWithOverloading.class);
43+
ctx.setAllowBeanDefinitionOverriding(false);
44+
ctx.refresh();
45+
assertThat(ctx.getBean(String.class), equalTo("regular"));
46+
}
5847

59-
@SuppressWarnings({ "hiding" })
60-
@Configuration class Config {
61-
@Bean String aString() { return "na"; }
62-
@Bean String aString(Integer dependency) { return "na"; }
63-
}
48+
@Test
49+
public void beanMethodOverloadingWithoutInheritanceAndExtraDependency() {
50+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
51+
ctx.register(ConfigWithOverloading.class);
52+
ctx.getDefaultListableBeanFactory().registerSingleton("anInt", 5);
53+
ctx.setAllowBeanDefinitionOverriding(false);
54+
ctx.refresh();
55+
assertThat(ctx.getBean(String.class), equalTo("overloaded5"));
56+
}
57+
58+
@Test
59+
public void beanMethodOverloadingWithAdditionalMetadata() {
60+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
61+
ctx.register(ConfigWithOverloadingAndAdditionalMetadata.class);
62+
ctx.setAllowBeanDefinitionOverriding(false);
63+
ctx.refresh();
64+
assertFalse(ctx.getDefaultListableBeanFactory().containsSingleton("aString"));
65+
assertThat(ctx.getBean(String.class), equalTo("regular"));
66+
assertTrue(ctx.getDefaultListableBeanFactory().containsSingleton("aString"));
67+
}
6468

65-
this.thrown.expect(BeanDefinitionParsingException.class);
66-
this.thrown.expectMessage("overloaded @Bean methods named 'aString'");
67-
new AnnotationConfigApplicationContext(Config.class);
69+
@Test
70+
public void beanMethodOverloadingWithAdditionalMetadataButOtherMethodExecuted() {
71+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
72+
ctx.register(ConfigWithOverloadingAndAdditionalMetadata.class);
73+
ctx.getDefaultListableBeanFactory().registerSingleton("anInt", 5);
74+
ctx.setAllowBeanDefinitionOverriding(false);
75+
ctx.refresh();
76+
assertFalse(ctx.getDefaultListableBeanFactory().containsSingleton("aString"));
77+
assertThat(ctx.getBean(String.class), equalTo("overloaded5"));
78+
assertTrue(ctx.getDefaultListableBeanFactory().containsSingleton("aString"));
6879
}
6980

7081
@Test
7182
public void beanMethodOverloadingWithInheritance() {
72-
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SubConfig.class);
83+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
84+
ctx.register(SubConfig.class);
85+
ctx.setAllowBeanDefinitionOverriding(false);
86+
ctx.refresh();
87+
assertFalse(ctx.getDefaultListableBeanFactory().containsSingleton("aString"));
7388
assertThat(ctx.getBean(String.class), equalTo("overloaded5"));
89+
assertTrue(ctx.getDefaultListableBeanFactory().containsSingleton("aString"));
7490
}
7591

92+
// SPR-11025
7693
@Test
7794
public void beanMethodOverloadingWithInheritanceAndList() {
78-
// SPR-11025
79-
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SubConfigWithList.class);
95+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
96+
ctx.register(SubConfigWithList.class);
97+
ctx.setAllowBeanDefinitionOverriding(false);
98+
ctx.refresh();
99+
assertFalse(ctx.getDefaultListableBeanFactory().containsSingleton("aString"));
80100
assertThat(ctx.getBean(String.class), equalTo("overloaded5"));
101+
assertTrue(ctx.getDefaultListableBeanFactory().containsSingleton("aString"));
81102
}
82103

83104
/**
@@ -91,11 +112,6 @@ public void beanMethodShadowing() {
91112
assertThat(ctx.getBean(String.class), equalTo("shadow"));
92113
}
93114

94-
/**
95-
* Tests that polymorphic Configuration classes need not explicitly redeclare the
96-
* {@link Configuration} annotation. This respects the {@link Inherited} nature
97-
* of the Configuration annotation, even though it's being detected via ASM.
98-
*/
99115
@Test
100116
public void beanMethodsDetectedOnSuperClass() {
101117
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
@@ -122,6 +138,36 @@ static class Config extends BaseConfig {
122138
}
123139

124140

141+
@Configuration
142+
static class ConfigWithOverloading {
143+
144+
@Bean
145+
String aString() {
146+
return "regular";
147+
}
148+
149+
@Bean
150+
String aString(Integer dependency) {
151+
return "overloaded" + dependency;
152+
}
153+
}
154+
155+
156+
@Configuration
157+
static class ConfigWithOverloadingAndAdditionalMetadata {
158+
159+
@Bean @Lazy
160+
String aString() {
161+
return "regular";
162+
}
163+
164+
@Bean
165+
String aString(Integer dependency) {
166+
return "overloaded" + dependency;
167+
}
168+
}
169+
170+
125171
@Configuration
126172
static class SuperConfig {
127173

@@ -140,7 +186,7 @@ Integer anInt() {
140186
return 5;
141187
}
142188

143-
@Bean
189+
@Bean @Lazy
144190
String aString(Integer dependency) {
145191
return "overloaded" + dependency;
146192
}
@@ -155,7 +201,7 @@ Integer anInt() {
155201
return 5;
156202
}
157203

158-
@Bean
204+
@Bean @Lazy
159205
String aString(List<Integer> dependency) {
160206
return "overloaded" + dependency.get(0);
161207
}

0 commit comments

Comments
 (0)