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