Prevent @Bean method overloading by default (with enforceUniqueMethods flag)

Closes gh-22609
This commit is contained in:
Juergen Hoeller 2022-02-17 22:37:34 +01:00
parent 41ee23345d
commit 4a470e0a37
5 changed files with 71 additions and 29 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -460,4 +460,16 @@ public @interface Configuration {
*/
boolean proxyBeanMethods() default true;
/**
* Specify whether {@code @Bean} methods need to have unique method names,
* raising an exception otherwise in order to prevent accidental overloading.
* <p>The default is {@code true}, preventing accidental method overloads which
* get interpreted as overloaded factory methods for the same bean definition
* (as opposed to separate bean definitions with individual conditions etc).
* Switch this flag to {@code false} in order to allow for method overloading
* according to those semantics, accepting the risk for accidental overlaps.
* @since 6.0
*/
boolean enforceUniqueMethods() default true;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -29,6 +29,7 @@ import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.core.io.DescriptiveResource;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@ -210,8 +211,9 @@ final class ConfigurationClass {
}
void validate(ProblemReporter problemReporter) {
// A configuration class may not be final (CGLIB limitation) unless it declares proxyBeanMethods=false
Map<String, Object> attributes = this.metadata.getAnnotationAttributes(Configuration.class.getName());
// A configuration class may not be final (CGLIB limitation) unless it declares proxyBeanMethods=false
if (attributes != null && (Boolean) attributes.get("proxyBeanMethods")) {
if (this.metadata.isFinal()) {
problemReporter.error(new FinalConfigurationProblem());
@ -220,6 +222,18 @@ final class ConfigurationClass {
beanMethod.validate(problemReporter);
}
}
// A configuration class may not contain overloaded bean methods unless it declares enforceUniqueMethods=false
if (attributes != null && (Boolean) attributes.get("enforceUniqueMethods")) {
Map<String, MethodMetadata> beanMethodsByName = new LinkedHashMap<>();
for (BeanMethod beanMethod : this.beanMethods) {
MethodMetadata current = beanMethod.getMetadata();
MethodMetadata existing = beanMethodsByName.put(current.getMethodName(), current);
if (existing != null && existing.getDeclaringClassName().equals(current.getDeclaringClassName())) {
problemReporter.error(new BeanMethodOverloadingProblem(existing.getMethodName()));
}
}
}
}
@Override
@ -250,4 +264,19 @@ final class ConfigurationClass {
}
}
/**
* Configuration classes are not allowed to contain overloaded bean methods
* by default (as of 6.0).
*/
private class BeanMethodOverloadingProblem extends Problem {
BeanMethodOverloadingProblem(String methodName) {
super(String.format("@Configuration class '%s' contains overloaded @Bean methods with name '%s'. Use " +
"unique method names for separate bean definitions (with individual conditions etc) " +
"or switch '@Configuration.enforceUniqueMethods' to 'false'.",
getSimpleName(), methodName), new Location(getResource(), getMetadata()));
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -967,8 +967,7 @@ class ConfigurationClassParser {
public Collection<SourceClass> getMemberClasses() throws IOException {
Object sourceToProcess = this.source;
if (sourceToProcess instanceof Class) {
Class<?> sourceClass = (Class<?>) sourceToProcess;
if (sourceToProcess instanceof Class<?> sourceClass) {
try {
Class<?>[] declaredClasses = sourceClass.getDeclaredClasses();
List<SourceClass> members = new ArrayList<>(declaredClasses.length);
@ -1013,8 +1012,7 @@ class ConfigurationClassParser {
public Set<SourceClass> getInterfaces() throws IOException {
Set<SourceClass> result = new LinkedHashSet<>();
if (this.source instanceof Class) {
Class<?> sourceClass = (Class<?>) this.source;
if (this.source instanceof Class<?> sourceClass) {
for (Class<?> ifcClass : sourceClass.getInterfaces()) {
result.add(asSourceClass(ifcClass, DEFAULT_EXCLUSION_FILTER));
}
@ -1029,8 +1027,7 @@ class ConfigurationClassParser {
public Set<SourceClass> getAnnotations() {
Set<SourceClass> result = new LinkedHashSet<>();
if (this.source instanceof Class) {
Class<?> sourceClass = (Class<?>) this.source;
if (this.source instanceof Class<?> sourceClass) {
for (Annotation ann : sourceClass.getDeclaredAnnotations()) {
Class<?> annType = ann.annotationType();
if (!annType.getName().startsWith("java")) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -41,7 +41,7 @@ public class BeanMethodPolymorphismTests {
@Test
public void beanMethodDetectedOnSuperClass() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
assertThat(ctx.getBean("testBean", TestBean.class)).isNotNull();
assertThat(ctx.getBean("testBean", BaseTestBean.class)).isNotNull();
}
@Test
@ -51,7 +51,7 @@ public class BeanMethodPolymorphismTests {
ctx.setAllowBeanDefinitionOverriding(false);
ctx.refresh();
assertThat(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")).isFalse();
assertThat(ctx.getBean("testBean", TestBean.class).toString()).isEqualTo("overridden");
assertThat(ctx.getBean("testBean", BaseTestBean.class).toString()).isEqualTo("overridden");
assertThat(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")).isTrue();
}
@ -62,7 +62,7 @@ public class BeanMethodPolymorphismTests {
ctx.setAllowBeanDefinitionOverriding(false);
ctx.refresh();
assertThat(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")).isFalse();
assertThat(ctx.getBean("testBean", TestBean.class).toString()).isEqualTo("overridden");
assertThat(ctx.getBean("testBean", BaseTestBean.class).toString()).isEqualTo("overridden");
assertThat(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")).isTrue();
}
@ -73,7 +73,7 @@ public class BeanMethodPolymorphismTests {
ctx.setAllowBeanDefinitionOverriding(false);
ctx.refresh();
assertThat(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")).isFalse();
assertThat(ctx.getBean("testBean", TestBean.class).toString()).isEqualTo("overridden");
assertThat(ctx.getBean("testBean", BaseTestBean.class).toString()).isEqualTo("overridden");
assertThat(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")).isTrue();
}
@ -84,7 +84,7 @@ public class BeanMethodPolymorphismTests {
ctx.setAllowBeanDefinitionOverriding(false);
ctx.refresh();
assertThat(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")).isFalse();
assertThat(ctx.getBean("testBean", TestBean.class).toString()).isEqualTo("overridden");
assertThat(ctx.getBean("testBean", BaseTestBean.class).toString()).isEqualTo("overridden");
assertThat(ctx.getDefaultListableBeanFactory().containsSingleton("testBean")).isTrue();
}
@ -171,7 +171,15 @@ public class BeanMethodPolymorphismTests {
ctx.register(AnnotationAwareAspectJAutoProxyCreator.class);
ctx.register(TestAdvisor.class);
ctx.refresh();
ctx.getBean("testBean", TestBean.class);
ctx.getBean("testBean", BaseTestBean.class);
}
static class BaseTestBean {
}
static class ExtendedTestBean extends BaseTestBean {
}
@ -179,8 +187,8 @@ public class BeanMethodPolymorphismTests {
static class BaseConfig {
@Bean
public TestBean testBean() {
return new TestBean();
public BaseTestBean testBean() {
return new BaseTestBean();
}
}
@ -195,8 +203,8 @@ public class BeanMethodPolymorphismTests {
@Bean @Lazy
@Override
public TestBean testBean() {
return new TestBean() {
public BaseTestBean testBean() {
return new BaseTestBean() {
@Override
public String toString() {
return "overridden";
@ -206,10 +214,6 @@ public class BeanMethodPolymorphismTests {
}
static class ExtendedTestBean extends TestBean {
}
@Configuration
static class NarrowedOverridingConfig extends BaseConfig {
@ -226,7 +230,7 @@ public class BeanMethodPolymorphismTests {
}
@Configuration
@Configuration(enforceUniqueMethods = false)
static class ConfigWithOverloading {
@Bean
@ -241,7 +245,7 @@ public class BeanMethodPolymorphismTests {
}
@Configuration
@Configuration(enforceUniqueMethods = false)
static class ConfigWithOverloadingAndAdditionalMetadata {
@Bean @Lazy

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -626,7 +626,7 @@ public class ConfigurationClassProcessingTests {
}
@Configuration
@Configuration(enforceUniqueMethods = false)
public static class OverloadedBeanMismatch {
@Bean(name = "other")