SPR-6158: Initial implementation and tests for @ImportXml

This commit is contained in:
Chris Beams
2009-11-07 00:32:40 +00:00
parent 20ec13ded5
commit 0a4463fb71
9 changed files with 208 additions and 25 deletions

View File

@@ -50,6 +50,8 @@ final class ConfigurationClass {
private String beanName;
private final Set<String> xmlFilesToImport = new LinkedHashSet<String>();
private final Set<ConfigurationClassMethod> methods = new LinkedHashSet<ConfigurationClassMethod>();
private final Map<String, Integer> overloadedMethodMap = new LinkedHashMap<String, Integer>();
@@ -61,7 +63,7 @@ final class ConfigurationClass {
this.beanName = beanName;
}
public ConfigurationClass(Class clazz, String beanName) {
public ConfigurationClass(Class<?> clazz, String beanName) {
this.metadata = new StandardAnnotationMetadata(clazz);
this.resource = new DescriptiveResource(clazz.toString());
this.beanName = beanName;
@@ -100,10 +102,19 @@ final class ConfigurationClass {
}
return this;
}
public Set<ConfigurationClassMethod> getConfigurationMethods() {
public Set<ConfigurationClassMethod> getMethods() {
return this.methods;
}
public void addXmlImport(String xmlImport) {
this.xmlFilesToImport.add(xmlImport);
}
public Set<String> getXmlImports() {
return this.xmlFilesToImport;
}
public void validate(ProblemReporter problemReporter) {
// No overloading of factory methods allowed

View File

@@ -37,7 +37,9 @@ import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
@@ -90,9 +92,11 @@ class ConfigurationClassBeanDefinitionReader {
*/
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass) {
doLoadBeanDefinitionForConfigurationClass(configClass);
for (ConfigurationClassMethod method : configClass.getConfigurationMethods()) {
for (ConfigurationClassMethod method : configClass.getMethods()) {
loadBeanDefinitionsForModelMethod(method);
}
loadBeanDefinitionsFromXml(configClass.getXmlImports());
}
/**
@@ -208,7 +212,11 @@ class ConfigurationClassBeanDefinitionReader {
registry.registerBeanDefinition(beanName, beanDefToRegister);
}
private void loadBeanDefinitionsFromXml(Set<String> xmlImports) {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this.registry);
reader.loadBeanDefinitions(xmlImports.toArray(new String[]{}));
}
/**
* {@link RootBeanDefinition} marker subclass used to signify that a bean definition
@@ -216,6 +224,7 @@ class ConfigurationClassBeanDefinitionReader {
* Used in bean overriding cases where it's necessary to determine whether the bean
* definition was created externally.
*/
@SuppressWarnings("serial")
private class ConfigurationClassBeanDefinition extends RootBeanDefinition implements AnnotatedBeanDefinition {
private AnnotationMetadata annotationMetadata;

View File

@@ -36,14 +36,18 @@ import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
/**
* Parses a {@link Configuration} class definition, populating a configuration model.
* This ASM-based implementation avoids reflection and eager class loading in order to
* interoperate effectively with lazy class loading in a Spring ApplicationContext.
*
* Parses a {@link Configuration} class definition, populating a model (collection) of
* {@link ConfigurationClass} objects (parsing a single Configuration class may result in
* any number of ConfigurationClass objects because one Configuration class may import
* another using the {@link Import} annotation).
*
* <p>This class helps separate the concern of parsing the structure of a Configuration
* class from the concern of registering {@link BeanDefinition} objects based on the
* content of that model.
*
* <p>This ASM-based implementation avoids reflection and eager class loading in order to
* interoperate effectively with lazy class loading in a Spring ApplicationContext.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.0
@@ -55,19 +59,19 @@ class ConfigurationClassParser {
private final ProblemReporter problemReporter;
private final Set<ConfigurationClass> model;
private final Stack<ConfigurationClass> importStack = new ImportStack();
private final Set<ConfigurationClass> configurationClasses =
new LinkedHashSet<ConfigurationClass>();
/**
* Create a new {@link ConfigurationClassParser} instance that will be used
* to populate a configuration model.
* to populate the set of configuration classes.
*/
public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory, ProblemReporter problemReporter) {
this.metadataReaderFactory = metadataReaderFactory;
this.problemReporter = problemReporter;
this.model = new LinkedHashSet<ConfigurationClass>();
}
@@ -84,11 +88,11 @@ class ConfigurationClassParser {
/**
* Parse the specified {@link Configuration @Configuration} class.
* @param clazz the Clazz to parse
* @param clazz the Class to parse
* @param beanName may be null, but if populated represents the bean id
* (assumes that this configuration class was configured via XML)
*/
public void parse(Class clazz, String beanName) throws IOException {
public void parse(Class<?> clazz, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(clazz, beanName));
}
@@ -100,7 +104,7 @@ class ConfigurationClassParser {
String superClassName = metadata.getSuperClassName();
if (superClassName != null && !Object.class.getName().equals(superClassName)) {
if (metadata instanceof StandardAnnotationMetadata) {
Class clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass();
Class<?> clazz = ((StandardAnnotationMetadata) metadata).getIntrospectedClass();
metadata = new StandardAnnotationMetadata(clazz.getSuperclass());
}
else {
@@ -112,25 +116,30 @@ class ConfigurationClassParser {
metadata = null;
}
}
if (this.model.contains(configClass) && configClass.getBeanName() != null) {
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.model.remove(configClass);
this.configurationClasses.remove(configClass);
}
this.model.add(configClass);
this.configurationClasses.add(configClass);
}
protected void doProcessConfigurationClass(ConfigurationClass configClass, AnnotationMetadata metadata) throws IOException {
if (metadata.isAnnotated(Import.class.getName())) {
processImport(configClass, (String[]) metadata.getAnnotationAttributes(Import.class.getName()).get("value"));
}
if (metadata.isAnnotated(ImportXml.class.getName())) {
for (String xmlImport : (String[]) metadata.getAnnotationAttributes(ImportXml.class.getName()).get("value")) {
configClass.addXmlImport(xmlImport);
}
}
Set<MethodMetadata> methods = metadata.getAnnotatedMethods(Bean.class.getName());
for (MethodMetadata methodMetadata : methods) {
configClass.addMethod(new ConfigurationClassMethod(methodMetadata, configClass));
}
}
public void processImport(ConfigurationClass configClass, String[] classesToImport) throws IOException {
private void processImport(ConfigurationClass configClass, String[] classesToImport) throws IOException {
if (this.importStack.contains(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack, configClass.getMetadata()));
}
@@ -156,17 +165,17 @@ class ConfigurationClassParser {
}
/**
* Recurse through the model validating each {@link ConfigurationClass}.
* Validate each {@link ConfigurationClass} object.
* @see ConfigurationClass#validate
*/
public void validate() {
for (ConfigurationClass configClass : this.model) {
for (ConfigurationClass configClass : this.configurationClasses) {
configClass.validate(this.problemReporter);
}
}
public Set<ConfigurationClass> getModel() {
return this.model;
public Set<ConfigurationClass> getConfigurationClasses() {
return this.configurationClasses;
}

View File

@@ -184,7 +184,7 @@ public class ConfigurationClassPostProcessor implements BeanFactoryPostProcessor
parser.validate();
// Read the model and create bean definitions based on its content
new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor).loadBeanDefinitions(parser.getModel());
new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor).loadBeanDefinitions(parser.getConfigurationClasses());
}
/**

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2002-2009 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface ImportXml {
String[] value();
Class<?> relativeTo() default void.class;
}