Skip to content

Commit 57f675c

Browse files
committed
Allow @bean method to override scanned class matching its return type
Closes gh-31052
1 parent d89e305 commit 57f675c

File tree

2 files changed

+46
-11
lines changed

2 files changed

+46
-11
lines changed

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
@@ -301,8 +301,12 @@ protected boolean isOverriddenByExistingDefinition(BeanMethod beanMethod, String
301301
}
302302

303303
// A bean definition resulting from a component scan can be silently overridden
304-
// by an @Bean method, as of 4.2...
305-
if (existingBeanDef instanceof ScannedGenericBeanDefinition) {
304+
// by an @Bean method - and as of 6.1, even when general overriding is disabled
305+
// as long as the bean class is the same.
306+
if (existingBeanDef instanceof ScannedGenericBeanDefinition scannedBeanDef) {
307+
if (beanMethod.getMetadata().getReturnTypeName().equals(scannedBeanDef.getBeanClassName())) {
308+
this.registry.removeBeanDefinition(beanName);
309+
}
306310
return false;
307311
}
308312

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

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.context.annotation;
1818

19-
import java.io.IOException;
2019
import java.lang.annotation.ElementType;
2120
import java.lang.annotation.Retention;
2221
import java.lang.annotation.RetentionPolicy;
@@ -209,7 +208,7 @@ void withScopedProxy() throws Exception {
209208
}
210209

211210
@Test
212-
void withScopedProxyThroughRegex() throws IOException, ClassNotFoundException {
211+
void withScopedProxyThroughRegex() {
213212
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
214213
ctx.register(ComponentScanWithScopedProxyThroughRegex.class);
215214
ctx.getBeanFactory().registerScope("myScope", new SimpleMapScope());
@@ -221,7 +220,7 @@ void withScopedProxyThroughRegex() throws IOException, ClassNotFoundException {
221220
}
222221

223222
@Test
224-
void withScopedProxyThroughAspectJPattern() throws IOException, ClassNotFoundException {
223+
void withScopedProxyThroughAspectJPattern() {
225224
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
226225
ctx.register(ComponentScanWithScopedProxyThroughAspectJPattern.class);
227226
ctx.getBeanFactory().registerScope("myScope", new SimpleMapScope());
@@ -233,21 +232,38 @@ void withScopedProxyThroughAspectJPattern() throws IOException, ClassNotFoundExc
233232
}
234233

235234
@Test
236-
void withMultipleAnnotationIncludeFilters1() throws IOException, ClassNotFoundException {
235+
void withMultipleAnnotationIncludeFilters1() {
237236
AnnotationConfigApplicationContext ctx =
238237
new AnnotationConfigApplicationContext(ComponentScanWithMultipleAnnotationIncludeFilters1.class);
239238
ctx.getBean(DefaultNamedComponent.class); // @CustomStereotype-annotated
240239
ctx.getBean(MessageBean.class); // @CustomComponent-annotated
241240
}
242241

243242
@Test
244-
void withMultipleAnnotationIncludeFilters2() throws IOException, ClassNotFoundException {
243+
void withMultipleAnnotationIncludeFilters2() {
245244
AnnotationConfigApplicationContext ctx =
246245
new AnnotationConfigApplicationContext(ComponentScanWithMultipleAnnotationIncludeFilters2.class);
247246
ctx.getBean(DefaultNamedComponent.class); // @CustomStereotype-annotated
248247
ctx.getBean(MessageBean.class); // @CustomComponent-annotated
249248
}
250249

250+
@Test
251+
void withBeanMethodOverride() {
252+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
253+
ctx.register(ComponentScanWithMultipleAnnotationIncludeFilters3.class);
254+
ctx.refresh();
255+
assertThat(ctx.getBean(DefaultNamedComponent.class).toString()).isEqualTo("overridden");
256+
}
257+
258+
@Test
259+
void withBeanMethodOverrideAndGeneralOverridingDisabled() {
260+
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
261+
ctx.getDefaultListableBeanFactory().setAllowBeanDefinitionOverriding(false);
262+
ctx.register(ComponentScanWithMultipleAnnotationIncludeFilters3.class);
263+
ctx.refresh();
264+
assertThat(ctx.getBean(DefaultNamedComponent.class).toString()).isEqualTo("overridden");
265+
}
266+
251267
@Test
252268
void withBasePackagesAndValueAlias() {
253269
AnnotationConfigApplicationContext ctx =
@@ -292,6 +308,7 @@ static class ComposedAnnotationConfig {
292308
static class MultipleComposedAnnotationsConfig {
293309
}
294310

311+
295312
static class AwareTypeFilter implements TypeFilter, EnvironmentAware,
296313
ResourceLoaderAware, BeanClassLoaderAware, BeanFactoryAware {
297314

@@ -329,10 +346,8 @@ public boolean match(MetadataReader metadataReader, MetadataReaderFactory metada
329346
assertThat(this.environment).isNotNull();
330347
return false;
331348
}
332-
333349
}
334350

335-
336351
}
337352

338353

@@ -461,11 +476,27 @@ class ComponentScanWithMultipleAnnotationIncludeFilters1 {}
461476
)
462477
class ComponentScanWithMultipleAnnotationIncludeFilters2 {}
463478

479+
@Configuration
480+
@ComponentScan(basePackages = "example.scannable",
481+
useDefaultFilters = false,
482+
includeFilters = @Filter({CustomStereotype.class, CustomComponent.class})
483+
)
484+
class ComponentScanWithMultipleAnnotationIncludeFilters3 {
485+
486+
@Bean
487+
public DefaultNamedComponent thoreau() {
488+
return new DefaultNamedComponent() {
489+
@Override
490+
public String toString() {
491+
return "overridden";
492+
}
493+
};
494+
}
495+
}
496+
464497
@Configuration
465498
@ComponentScan(
466499
value = "example.scannable",
467500
basePackages = "example.scannable",
468501
basePackageClasses = example.scannable.PackageMarker.class)
469502
class ComponentScanWithBasePackagesAndValueAlias {}
470-
471-

0 commit comments

Comments
 (0)