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")) {