SPR-6158: Initial implementation and tests for @ImportXml

master
Chris Beams 15 years ago
parent 20ec13ded5
commit 0a4463fb71
  1. 6
      org.springframework.context/.project
  2. 14
      org.springframework.context/.springBeans
  3. 17
      org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java
  4. 13
      org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java
  5. 47
      org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java
  6. 2
      org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java
  7. 34
      org.springframework.context/src/main/java/org/springframework/context/annotation/ImportXml.java
  8. 10
      org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml
  9. 90
      org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportXmlTests.java

@ -10,8 +10,14 @@
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.springframework.ide.eclipse.core.springbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.springframework.ide.eclipse.core.springnature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<beansProjectDescription>
<version>1</version>
<pluginVersion><![CDATA[2.2.7.200910141010-RELEASE]]></pluginVersion>
<configSuffixes>
<configSuffix><![CDATA[xml]]></configSuffix>
</configSuffixes>
<enableImports><![CDATA[false]]></enableImports>
<configs>
<config>src/test/java/org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml</config>
</configs>
<configSets>
</configSets>
</beansProjectDescription>

@ -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

@ -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;

@ -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;
}

@ -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());
}
/**

@ -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;
}

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="xmlDeclaredBean" class="org.springframework.beans.TestBean">
<constructor-arg value="xml.declaraed"/>
</bean>
</beans>

@ -0,0 +1,90 @@
/*
* 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.configuration;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportXml;
import test.beans.TestBean;
/**
* Integration tests for {@link ImportXml} support.
*
* @author Chris Beams
*/
public class ImportXmlTests {
@Test
public void testImportXmlWorks() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportXmlConfig.class);
assertTrue("did not contain java-declared bean", ctx.containsBean("javaDeclaredBean"));
assertTrue("did not contain xml-declared bean", ctx.containsBean("xmlDeclaredBean"));
}
@Configuration
@ImportXml("classpath:org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml")
static class ImportXmlConfig {
public @Bean TestBean javaDeclaredBean() {
return new TestBean("java.declared");
}
}
// -------------------------------------------------------------------------
@Ignore
@Test
public void testImportXmlWorksWithRelativePathing() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportsXmlWithRelativeTo.class);
assertTrue("did not contain java-declared bean", ctx.containsBean("javaDeclaredBean"));
assertTrue("did not contain xml-declared bean", ctx.containsBean("xmlDeclaredBean"));
}
@Configuration
@ImportXml(value="beans.xml", relativeTo=ImportXmlTests.class)
static class ImportsXmlWithRelativeTo {
public @Bean TestBean javaDeclaredBean() {
return new TestBean("java.declared");
}
}
@Ignore
@Test
public void testImportXmlWorksWithAutowired() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AutowiredImportXml.class);
String name = ctx.getBean("xmlBeanName", String.class);
assertThat(name, equalTo("xmlBean"));
}
@Configuration
@ImportXml(value="beans.xml", relativeTo=AutowiredImportXml.class)
static class AutowiredImportXml {
@Autowired TestBean xmlDeclaredBean;
public @Bean String xmlBeanName() {
return xmlDeclaredBean.getName();
}
}
}
Loading…
Cancel
Save