diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactory.java index a1fbbb997..6d1c0658b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactory.java @@ -17,6 +17,8 @@ package org.springframework.batch.core.configuration.support; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; @@ -41,6 +43,10 @@ public class ClassPathXmlApplicationContextFactory implements ApplicationContext private ResourceXmlApplicationContext context; + private boolean copyConfiguration = true; + + private boolean copyBeanFactoryPostProcessors = true; + private final Object lock = new Object(); /** @@ -54,6 +60,27 @@ public class ClassPathXmlApplicationContextFactory implements ApplicationContext this.path = path; } + /** + * Flag to indicate that configuration such as bean post processors and + * custom editors should be copied from the parent context. Defaults to + * true; + * + * @param copyConfiguration the flag value to set + */ + public void setCopyConfiguration(boolean copyConfiguration) { + this.copyConfiguration = copyConfiguration; + } + + /** + * Flag to indicate that bean factory post processors (like property + * placeholders) should be copied from the parent context. Defaults to true; + * + * @param copyBeanFactoryPostProcessors the flag value to set + */ + public void setCopyBeanFactoryPostProcessors(boolean copyBeanFactoryPostProcessors) { + this.copyBeanFactoryPostProcessors = copyBeanFactoryPostProcessors; + } + /** * Setter for the parent application context. * @@ -92,17 +119,81 @@ public class ClassPathXmlApplicationContextFactory implements ApplicationContext * */ private final class ResourceXmlApplicationContext extends AbstractXmlApplicationContext { + + private final DefaultListableBeanFactory parentBeanFactory; + /** * @param parent */ - private ResourceXmlApplicationContext(ApplicationContext parent) { + public ResourceXmlApplicationContext(ConfigurableApplicationContext parent) { super(parent); + if (parent != null) { + Assert.isTrue(parent.getBeanFactory() instanceof DefaultListableBeanFactory, + "The parent application context must have a bean factory of type DefaultListableBeanFactory"); + parentBeanFactory = (DefaultListableBeanFactory) parent.getBeanFactory(); + refreshBeanFactory(); + prepareContext(parent, this); + } + else { + parentBeanFactory = null; + } refresh(); } + @Override + protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { + super.customizeBeanFactory(beanFactory); + if (parentBeanFactory != null) { + ClassPathXmlApplicationContextFactory.this.prepareBeanFactory(parentBeanFactory, beanFactory); + } + } + + @Override protected Resource[] getConfigResources() { return new Resource[] { path }; } + + } + + /** + * Extension point for special subclasses that want to do more complex + * things with the context prior to refresh. The default implementation + * copies bean factory post processors according to the flag set. The bean + * factory for the context will be available if needed through + * {@link ConfigurableApplicationContext#getBeanFactory() + * context.getBeanFactory()}. + * + * @param parent the parent for the new application context + * @param context the new application context before it is refreshed, but + * after bean factory is initialized + * + * @see ClassPathXmlApplicationContextFactory#setCopyBeanFactoryPostProcessors(boolean) + */ + protected void prepareContext(ConfigurableApplicationContext parent, ConfigurableApplicationContext context) { + if (copyBeanFactoryPostProcessors) { + for (String name : parent.getBeanNamesForType(BeanFactoryPostProcessor.class)) { + context.addBeanFactoryPostProcessor((BeanFactoryPostProcessor) parent.getBean(name)); + } + } + } + + /** + * Extension point for special subclasses that want to do more complex + * things with the bean factory prior to refresh. The default implementation + * copies all configuration from the parent according to the flag set. + * + * @param parent the parent bean factory for the new context (will never be + * null) + * @param beanFactory the new bean factory before bean definitions are + * loaded + * + * @see ClassPathXmlApplicationContextFactory#setCopyConfiguration(boolean) + * @see DefaultListableBeanFactory#copyConfigurationFrom(ConfigurableBeanFactory) + */ + protected void prepareBeanFactory(DefaultListableBeanFactory parent, DefaultListableBeanFactory beanFactory) { + if (copyConfiguration && parent != null) { + beanFactory.copyConfigurationFrom(parent); + } } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactoryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactoryTests.java index 998513aae..6f48cbafc 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactoryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlApplicationContextFactoryTests.java @@ -15,9 +15,13 @@ */ package org.springframework.batch.core.configuration.support; -import junit.framework.TestCase; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import org.junit.Test; import org.springframework.batch.core.Job; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.ClassPathResource; import org.springframework.util.ClassUtils; @@ -25,18 +29,60 @@ import org.springframework.util.ClassUtils; * @author Dave Syer * */ -public class ClassPathXmlApplicationContextFactoryTests extends TestCase { +public class ClassPathXmlApplicationContextFactoryTests { private ClassPathXmlApplicationContextFactory factory = new ClassPathXmlApplicationContextFactory(); + @Test public void testCreateJob() { factory.setPath(new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), "trivial-context.xml"))); assertNotNull(factory.createApplicationContext()); } + @Test public void testGetJobName() { factory.setPath(new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), "trivial-context.xml"))); assertEquals("test-job", factory.createApplicationContext().getBeanNamesForType(Job.class)[0]); } + @Test + public void testParentConfigurationInherited() { + factory.setApplicationContext(new ClassPathXmlApplicationContext(ClassUtils.addResourcePathToPackagePath(getClass(), "parent-context.xml"))); + factory.setPath(new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), "child-context.xml"))); + ConfigurableApplicationContext context = factory.createApplicationContext(); + assertEquals("test-job", context.getBeanNamesForType(Job.class)[0]); + assertEquals("bar", ((Job) context.getBean("test-job", Job.class)).getName()); + assertEquals(4, ((Foo) context.getBean("foo", Foo.class)).values[1], 0.01); + } + + @Test + public void testBeanFactoryPostProcessorsNotCopied() { + factory.setApplicationContext(new ClassPathXmlApplicationContext(ClassUtils.addResourcePathToPackagePath(getClass(), "parent-context.xml"))); + factory.setPath(new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), "child-context.xml"))); + factory.setCopyBeanFactoryPostProcessors(false); + ConfigurableApplicationContext context = factory.createApplicationContext(); + assertEquals("test-job", context.getBeanNamesForType(Job.class)[0]); + assertEquals("${foo}", ((Job) context.getBean("test-job", Job.class)).getName()); + assertEquals(4, ((Foo) context.getBean("foo", Foo.class)).values[1], 0.01); + } + + @Test + public void testBeanFactoryConfigurationNotCopied() { + factory.setApplicationContext(new ClassPathXmlApplicationContext(ClassUtils.addResourcePathToPackagePath(getClass(), "parent-context.xml"))); + factory.setPath(new ClassPathResource(ClassUtils.addResourcePathToPackagePath(getClass(), "child-context.xml"))); + factory.setCopyConfiguration(false); + ConfigurableApplicationContext context = factory.createApplicationContext(); + assertEquals("test-job", context.getBeanNamesForType(Job.class)[0]); + assertEquals("bar", ((Job) context.getBean("test-job", Job.class)).getName()); + // The CustomEditorConfigurer is a BeanFactoryPostProcessor so the editor gets copied anyway! + assertEquals(4, ((Foo) context.getBean("foo", Foo.class)).values[1], 0.01); + } + + public static class Foo { + private double[] values; + public void setValues(double[] values) { + this.values = values; + } + } + } diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/child-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/child-context.xml new file mode 100644 index 000000000..795d4ea11 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/child-context.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/parent-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/parent-context.xml new file mode 100644 index 000000000..c88fec662 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/support/parent-context.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + +