SPR-6158: Initial implementation and tests for @ImportXml
This commit is contained in:
parent
20ec13ded5
commit
0a4463fb71
|
|
@ -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…
Reference in New Issue