Properly handle knownSuperclasses in case of an overridden ConfigurationClass

Issue: SPR-10546
This commit is contained in:
Juergen Hoeller 2013-05-15 13:47:03 +02:00
parent 325d8834e3
commit 6e4317ebfb
3 changed files with 72 additions and 122 deletions

View File

@ -36,8 +36,8 @@ import org.springframework.util.ClassUtils;
/** /**
* Represents a user-defined {@link Configuration @Configuration} class. * Represents a user-defined {@link Configuration @Configuration} class.
* Includes a set of {@link Bean} methods, including all such methods defined in the * Includes a set of {@link Bean} methods, including all such methods
* ancestry of the class, in a 'flattened-out' manner. * defined in the ancestry of the class, in a 'flattened-out' manner.
* *
* @author Chris Beams * @author Chris Beams
* @author Juergen Hoeller * @author Juergen Hoeller
@ -51,15 +51,15 @@ final class ConfigurationClass {
private final Resource resource; private final Resource resource;
private final Map<String, Class<? extends BeanDefinitionReader>> importedResources =
new LinkedHashMap<String, Class<? extends BeanDefinitionReader>>();
private final Set<BeanMethod> beanMethods = new LinkedHashSet<BeanMethod>();
private String beanName; private String beanName;
private final boolean imported; private final boolean imported;
private final Set<BeanMethod> beanMethods = new LinkedHashSet<BeanMethod>();
private final Map<String, Class<? extends BeanDefinitionReader>> importedResources =
new LinkedHashMap<String, Class<? extends BeanDefinitionReader>>();
/** /**
* Create a new {@link ConfigurationClass} with the given name. * Create a new {@link ConfigurationClass} with the given name.
@ -98,7 +98,7 @@ final class ConfigurationClass {
* @see ConfigurationClass#ConfigurationClass(Class, boolean) * @see ConfigurationClass#ConfigurationClass(Class, boolean)
*/ */
public ConfigurationClass(Class<?> clazz, String beanName) { public ConfigurationClass(Class<?> clazz, String beanName) {
Assert.hasText(beanName, "bean name must not be null"); Assert.hasText(beanName, "Bean name must not be null");
this.metadata = new StandardAnnotationMetadata(clazz, true); this.metadata = new StandardAnnotationMetadata(clazz, true);
this.resource = new DescriptiveResource(clazz.toString()); this.resource = new DescriptiveResource(clazz.toString());
this.beanName = beanName; this.beanName = beanName;
@ -132,6 +132,14 @@ final class ConfigurationClass {
return ClassUtils.getShortName(getMetadata().getClassName()); return ClassUtils.getShortName(getMetadata().getClassName());
} }
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public String getBeanName() {
return this.beanName;
}
/** /**
* Return whether this configuration class was registered via @{@link Import} or * Return whether this configuration class was registered via @{@link Import} or
* automatically registered due to being nested within another configuration class. * automatically registered due to being nested within another configuration class.
@ -141,14 +149,6 @@ final class ConfigurationClass {
return this.imported; return this.imported;
} }
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public String getBeanName() {
return this.beanName;
}
public void addBeanMethod(BeanMethod method) { public void addBeanMethod(BeanMethod method) {
this.beanMethods.add(method); this.beanMethods.add(method);
} }
@ -157,8 +157,7 @@ final class ConfigurationClass {
return this.beanMethods; return this.beanMethods;
} }
public void addImportedResource( public void addImportedResource(String importedResource, Class<? extends BeanDefinitionReader> readerClass) {
String importedResource, Class<? extends BeanDefinitionReader> readerClass) {
this.importedResources.put(importedResource, readerClass); this.importedResources.put(importedResource, readerClass);
} }
@ -166,28 +165,8 @@ final class ConfigurationClass {
return this.importedResources; return this.importedResources;
} }
public void validate(ProblemReporter problemReporter) { public void validate(ProblemReporter problemReporter) {
// An @Bean method may only be overloaded through inheritance. No single
// @Configuration class may declare two @Bean methods with the same name.
final char hashDelim = '#';
Map<String, Integer> methodNameCounts = new HashMap<String, Integer>();
for (BeanMethod beanMethod : beanMethods) {
String dClassName = beanMethod.getMetadata().getDeclaringClassName();
String methodName = beanMethod.getMetadata().getMethodName();
String fqMethodName = dClassName + hashDelim + methodName;
Integer currentCount = methodNameCounts.get(fqMethodName);
int newCount = currentCount != null ? currentCount + 1 : 1;
methodNameCounts.put(fqMethodName, newCount);
}
for (String methodName : methodNameCounts.keySet()) {
int count = methodNameCounts.get(methodName);
if (count > 1) {
String shortMethodName = methodName.substring(methodName.indexOf(hashDelim)+1);
problemReporter.error(new BeanMethodOverloadingProblem(shortMethodName, count));
}
}
// A configuration class may not be final (CGLIB limitation) // A configuration class may not be final (CGLIB limitation)
if (getMetadata().isAnnotated(Configuration.class.getName())) { if (getMetadata().isAnnotated(Configuration.class.getName())) {
if (getMetadata().isFinal()) { if (getMetadata().isFinal()) {
@ -195,6 +174,23 @@ final class ConfigurationClass {
} }
} }
// An @Bean method may only be overloaded through inheritance. No single
// @Configuration class may declare two @Bean methods with the same name.
Map<String, Integer> methodNameCounts = new HashMap<String, Integer>();
for (BeanMethod beanMethod : this.beanMethods) {
String fqMethodName = beanMethod.getFullyQualifiedMethodName();
Integer currentCount = methodNameCounts.get(fqMethodName);
int newCount = currentCount != null ? currentCount + 1 : 1;
methodNameCounts.put(fqMethodName, newCount);
}
for (String fqMethodName : methodNameCounts.keySet()) {
int count = methodNameCounts.get(fqMethodName);
if (count > 1) {
String shortMethodName = ConfigurationMethod.getShortMethodName(fqMethodName);
problemReporter.error(new BeanMethodOverloadingProblem(shortMethodName, count));
}
}
for (BeanMethod beanMethod : this.beanMethods) { for (BeanMethod beanMethod : this.beanMethods) {
beanMethod.validate(problemReporter); beanMethod.validate(problemReporter);
} }

View File

@ -23,12 +23,9 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.Stack; import java.util.Stack;
@ -79,7 +76,6 @@ import static org.springframework.context.annotation.MetadataUtils.*;
* *
* @author Chris Beams * @author Chris Beams
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Rob Winch
* @since 3.0 * @since 3.0
* @see ConfigurationClassBeanDefinitionReader * @see ConfigurationClassBeanDefinitionReader
*/ */
@ -91,14 +87,6 @@ class ConfigurationClassParser {
private final ImportStack importStack = new ImportStack(); private final ImportStack importStack = new ImportStack();
private final Set<String> knownSuperclasses = new LinkedHashSet<String>();
private final Map<ConfigurationClass,ConfigurationClass> configurationClasses =
new LinkedHashMap<ConfigurationClass,ConfigurationClass>();
private final Stack<PropertySource<?>> propertySources =
new Stack<PropertySource<?>>();
private final Environment environment; private final Environment environment;
private final ResourceLoader resourceLoader; private final ResourceLoader resourceLoader;
@ -107,6 +95,12 @@ class ConfigurationClassParser {
private final ComponentScanAnnotationParser componentScanParser; private final ComponentScanAnnotationParser componentScanParser;
private final Set<ConfigurationClass> configurationClasses = new LinkedHashSet<ConfigurationClass>();
private final Map<String, ConfigurationClass> knownSuperclasses = new HashMap<String, ConfigurationClass>();
private final Stack<PropertySource<?>> propertySources = new Stack<PropertySource<?>>();
/** /**
* Create a new {@link ConfigurationClassParser} instance that will be used * Create a new {@link ConfigurationClassParser} instance that will be used
@ -155,70 +149,28 @@ class ConfigurationClassParser {
} }
} }
// recursively process the configuration class and its superclass hierarchy if (this.configurationClasses.contains(configClass) && configClass.getBeanName() != null) {
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
this.configurationClasses.remove(configClass);
for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext();) {
if (configClass.equals(it.next())) {
it.remove();
}
}
}
// Recursively process the configuration class and its superclass hierarchy.
do { do {
metadata = doProcessConfigurationClass(configClass, metadata); metadata = doProcessConfigurationClass(configClass, metadata);
} }
while (metadata != null); while (metadata != null);
if (getConfigurationClasses().contains(configClass) && configClass.getBeanName() != null) { this.configurationClasses.add(configClass);
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
ConfigurationClass originalConfigClass = removeConfigurationClass(configClass);
mergeFromOriginalConfig(originalConfigClass,configClass);
}
addConfigurationClass(configClass);
}
/**
* Merges from the original {@link ConfigurationClass} to the new
* {@link ConfigurationClass}. This is necessary if parent classes have already been
* processed.
*
* @param originalConfigClass the original {@link ConfigurationClass} that may have
* additional metadata
* @param configClass the new {@link ConfigurationClass} that will have metadata added
* to it if necessary
*/
private void mergeFromOriginalConfig(ConfigurationClass originalConfigClass,
ConfigurationClass configClass) {
Set<String> beanMethodNames = new HashSet<String>();
for(BeanMethod beanMethod : configClass.getBeanMethods()) {
beanMethodNames.add(createBeanMethodName(beanMethod));
}
for(BeanMethod originalBeanMethod : originalConfigClass.getBeanMethods()) {
String originalBeanMethodName = createBeanMethodName(originalBeanMethod);
if(!beanMethodNames.contains(originalBeanMethodName)) {
configClass.addBeanMethod(new BeanMethod(originalBeanMethod.getMetadata(), configClass));
}
}
for(Entry<String, Class<? extends BeanDefinitionReader>> originalImportedEntry : originalConfigClass.getImportedResources().entrySet()) {
if(!configClass.getImportedResources().containsKey(originalImportedEntry.getKey())) {
configClass.addImportedResource(originalImportedEntry.getKey(), originalImportedEntry.getValue());
}
}
} }
/** /**
* Converts a {@link BeanMethod} into the fully qualified name of the Method * @return annotation metadata of superclass, {@code null} if none found or previously processed
*
* @param beanMethod
* @return fully qualified name of the {@link BeanMethod}
*/
private String createBeanMethodName(BeanMethod beanMethod) {
String hashDelim = "#";
String dClassName = beanMethod.getMetadata().getDeclaringClassName();
String methodName = beanMethod.getMetadata().getMethodName();
return dClassName + hashDelim + methodName;
}
/**
* @return annotation metadata of superclass, null if none found or previously processed
*/ */
protected AnnotationMetadata doProcessConfigurationClass( protected AnnotationMetadata doProcessConfigurationClass(
ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException { ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException {
@ -232,7 +184,7 @@ class ConfigurationClassParser {
processPropertySource(propertySource); processPropertySource(propertySource);
} }
// process any @ComponentScan annotions // process any @ComponentScan annotations
AnnotationAttributes componentScan = attributesFor(metadata, ComponentScan.class); AnnotationAttributes componentScan = attributesFor(metadata, ComponentScan.class);
if (componentScan != null) { if (componentScan != null) {
// the config class is annotated with @ComponentScan -> perform the scan immediately // the config class is annotated with @ComponentScan -> perform the scan immediately
@ -248,7 +200,6 @@ class ConfigurationClassParser {
} }
// process any @Import annotations // process any @Import annotations
Set<Object> imports = new LinkedHashSet<Object>(); Set<Object> imports = new LinkedHashSet<Object>();
Set<Object> visited = new LinkedHashSet<Object>(); Set<Object> visited = new LinkedHashSet<Object>();
collectImports(metadata, imports, visited); collectImports(metadata, imports, visited);
@ -275,7 +226,8 @@ class ConfigurationClassParser {
// process superclass, if any // process superclass, if any
if (metadata.hasSuperClass()) { if (metadata.hasSuperClass()) {
String superclass = metadata.getSuperClassName(); String superclass = metadata.getSuperClassName();
if (this.knownSuperclasses.add(superclass)) { if (!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// superclass found, return its annotation metadata and recurse // superclass found, return its annotation metadata and recurse
if (metadata instanceof StandardAnnotationMetadata) { if (metadata instanceof StandardAnnotationMetadata) {
Class<?> clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass(); Class<?> clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass();
@ -302,14 +254,6 @@ class ConfigurationClassParser {
return null; return null;
} }
private void addConfigurationClass(ConfigurationClass configClass) {
this.configurationClasses.put(configClass,configClass);
}
private ConfigurationClass removeConfigurationClass(ConfigurationClass configClass) {
return this.configurationClasses.remove(configClass);
}
/** /**
* Register member (nested) classes that happen to be configuration classes themselves. * Register member (nested) classes that happen to be configuration classes themselves.
* @param metadata the metadata representation of the containing class * @param metadata the metadata representation of the containing class
@ -500,13 +444,13 @@ class ConfigurationClassParser {
* @see ConfigurationClass#validate * @see ConfigurationClass#validate
*/ */
public void validate() { public void validate() {
for (ConfigurationClass configClass : getConfigurationClasses()) { for (ConfigurationClass configClass : this.configurationClasses) {
configClass.validate(this.problemReporter); configClass.validate(this.problemReporter);
} }
} }
public Set<ConfigurationClass> getConfigurationClasses() { public Set<ConfigurationClass> getConfigurationClasses() {
return this.configurationClasses.keySet(); return this.configurationClasses;
} }
public Stack<PropertySource<?>> getPropertySources() { public Stack<PropertySource<?>> getPropertySources() {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2011 the original author or authors. * Copyright 2002-2013 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -36,6 +36,7 @@ abstract class ConfigurationMethod {
this.configurationClass = configurationClass; this.configurationClass = configurationClass;
} }
public MethodMetadata getMetadata() { public MethodMetadata getMetadata() {
return this.metadata; return this.metadata;
} }
@ -48,13 +49,22 @@ abstract class ConfigurationMethod {
return new Location(this.configurationClass.getResource(), this.metadata); return new Location(this.configurationClass.getResource(), this.metadata);
} }
String getFullyQualifiedMethodName() {
return this.metadata.getDeclaringClassName() + "#" + this.metadata.getMethodName();
}
static String getShortMethodName(String fullyQualifiedMethodName) {
return fullyQualifiedMethodName.substring(fullyQualifiedMethodName.indexOf('#') + 1);
}
public void validate(ProblemReporter problemReporter) { public void validate(ProblemReporter problemReporter) {
} }
@Override @Override
public String toString() { public String toString() {
return String.format("[%s:name=%s,declaringClass=%s]", return String.format("[%s:name=%s,declaringClass=%s]",
this.getClass().getSimpleName(), this.getMetadata().getMethodName(), this.getMetadata().getDeclaringClassName()); getClass().getSimpleName(), getMetadata().getMethodName(), getMetadata().getDeclaringClassName());
} }
} }