From 0a4463fb71e6901a943a5bafc9d1957da2229333 Mon Sep 17 00:00:00 2001 From: Chris Beams Date: Sat, 7 Nov 2009 00:32:40 +0000 Subject: [PATCH] SPR-6158: Initial implementation and tests for @ImportXml --- org.springframework.context/.project | 6 ++ org.springframework.context/.springBeans | 14 +++ .../annotation/ConfigurationClass.java | 17 +++- ...onfigurationClassBeanDefinitionReader.java | 13 ++- .../annotation/ConfigurationClassParser.java | 47 ++++++---- .../ConfigurationClassPostProcessor.java | 2 +- .../context/annotation/ImportXml.java | 34 +++++++ .../configuration/ImportXmlConfig-context.xml | 10 +++ .../configuration/ImportXmlTests.java | 90 +++++++++++++++++++ 9 files changed, 208 insertions(+), 25 deletions(-) create mode 100644 org.springframework.context/.springBeans create mode 100644 org.springframework.context/src/main/java/org/springframework/context/annotation/ImportXml.java create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml create mode 100644 org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportXmlTests.java diff --git a/org.springframework.context/.project b/org.springframework.context/.project index 6f09d98947..4ca0e0f5c3 100644 --- a/org.springframework.context/.project +++ b/org.springframework.context/.project @@ -10,8 +10,14 @@ + + org.springframework.ide.eclipse.core.springbuilder + + + + org.springframework.ide.eclipse.core.springnature org.eclipse.jdt.core.javanature diff --git a/org.springframework.context/.springBeans b/org.springframework.context/.springBeans new file mode 100644 index 0000000000..2bd66c4f13 --- /dev/null +++ b/org.springframework.context/.springBeans @@ -0,0 +1,14 @@ + + + 1 + + + + + + + src/test/java/org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml + + + + diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java index aa7bb0cb41..f56edc8152 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClass.java @@ -50,6 +50,8 @@ final class ConfigurationClass { private String beanName; + private final Set xmlFilesToImport = new LinkedHashSet(); + private final Set methods = new LinkedHashSet(); private final Map overloadedMethodMap = new LinkedHashMap(); @@ -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 getConfigurationMethods() { + + public Set getMethods() { return this.methods; } + + public void addXmlImport(String xmlImport) { + this.xmlFilesToImport.add(xmlImport); + } + + public Set getXmlImports() { + return this.xmlFilesToImport; + } + public void validate(ProblemReporter problemReporter) { // No overloading of factory methods allowed diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java index 3b5f490e22..90d5765070 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassBeanDefinitionReader.java @@ -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 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; diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index 64ed5bd3e1..ddbe1b5e4f 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -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). + * *

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. * + *

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 model; - private final Stack importStack = new ImportStack(); + + private final Set configurationClasses = + new LinkedHashSet(); /** * 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(); } @@ -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 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 getModel() { - return this.model; + public Set getConfigurationClasses() { + return this.configurationClasses; } diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index 32170d76db..eee004a859 100644 --- a/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -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()); } /** diff --git a/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportXml.java b/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportXml.java new file mode 100644 index 0000000000..108d0cb670 --- /dev/null +++ b/org.springframework.context/src/main/java/org/springframework/context/annotation/ImportXml.java @@ -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; + +} diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml new file mode 100644 index 0000000000..c9b3968c32 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportXmlConfig-context.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportXmlTests.java b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportXmlTests.java new file mode 100644 index 0000000000..ce193849d8 --- /dev/null +++ b/org.springframework.context/src/test/java/org/springframework/context/annotation/configuration/ImportXmlTests.java @@ -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(); + } + } + +} \ No newline at end of file