diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java index 9479221f6..09e143245 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java @@ -54,7 +54,7 @@ public class ApplicationContextJobFactory implements JobFactory { public Job createJob() { ConfigurableApplicationContext context = applicationContextFactory.createApplicationContext(); Job job = (Job) context.getBean(jobName, Job.class); - return new ContextClosingJob(job, context); + return new ContextAwareJob(job); } /** @@ -72,19 +72,15 @@ public class ApplicationContextJobFactory implements JobFactory { * @author Dave Syer * */ - private static class ContextClosingJob implements Job { + private static class ContextAwareJob implements Job { private Job delegate; - private ConfigurableApplicationContext context; - /** * @param delegate - * @param context */ - public ContextClosingJob(Job delegate, ConfigurableApplicationContext context) { + public ContextAwareJob(Job delegate) { super(); this.delegate = delegate; - this.context = context; } /** @@ -92,12 +88,7 @@ public class ApplicationContextJobFactory implements JobFactory { * @see org.springframework.batch.core.Job#execute(org.springframework.batch.core.JobExecution) */ public void execute(JobExecution execution) { - try { - delegate.execute(execution); - } - finally { - context.close(); - } + delegate.execute(execution); } /** 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 61aede03d..a1fbbb997 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 @@ -25,10 +25,12 @@ import org.springframework.core.io.Resource; import org.springframework.util.Assert; /** - * {@link ApplicationContextFactory} implementation that takes a parent context and a path - * to the context to create. Each time the createApplicationContext method is called, a new - * {@link ApplicationContext} will be returned. It should be noted that if a path isn't - * set, the parent will always be returned. + * {@link ApplicationContextFactory} implementation that takes a parent context + * and a path to the context to create. When createApplicationContext method is + * called, the child {@link ApplicationContext} will be returned. The child + * context is not re-created every time it is requested, it is lazily + * initialized and cached. Clients should ensure that it is closed when it is no + * longer needed. If a path is not set, the parent will always be returned. * */ public class ClassPathXmlApplicationContextFactory implements ApplicationContextFactory, ApplicationContextAware { @@ -37,10 +39,14 @@ public class ClassPathXmlApplicationContextFactory implements ApplicationContext private Resource path; + private ResourceXmlApplicationContext context; + + private final Object lock = new Object(); + /** * Setter for the path to the xml to load to create an - * {@link ApplicationContext}. Use imports to centralise the configuration in - * one file. + * {@link ApplicationContext}. Use imports to centralise the configuration + * in one file. * * @param path the resource path to the xml to load for the child context. */ @@ -64,10 +70,21 @@ public class ClassPathXmlApplicationContextFactory implements ApplicationContext * @see ApplicationContextFactory#createApplicationContext() */ public ConfigurableApplicationContext createApplicationContext() { + if (path == null) { return parent; } - return new ResourceXmlApplicationContext(parent); + + if (context == null) { + // Lazy initialization of cached context + synchronized (lock) { + if (context == null) { + context = new ResourceXmlApplicationContext(parent); + } + } + } + return context; + } /** @@ -84,7 +101,7 @@ public class ClassPathXmlApplicationContextFactory implements ApplicationContext } protected Resource[] getConfigResources() { - return new Resource[] {path}; + return new Resource[] { path }; } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistry.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistry.java index 501211216..9377feb05 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistry.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistry.java @@ -18,6 +18,7 @@ package org.springframework.batch.core.configuration.support; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; import org.apache.commons.logging.Log; @@ -28,20 +29,26 @@ import org.springframework.batch.core.configuration.JobFactory; import org.springframework.batch.core.configuration.ListableJobRegistry; import org.springframework.batch.core.launch.NoSuchJobException; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.io.Resource; /** * Implementation of the {@link ListableJobRegistry} interface that assumes all - * Jobs will be loaded from ClassPathXml resources. + * Jobs will be loaded from class path xml resources. Each resource provided is + * loaded as an application context with the current context as its parent, and + * then all the jobs from the child context are registered under their bean + * names. Care must be taken to avoid duplicate names. * * @author Lucas Ward * @author Dave Syer * @since 2.0 */ -public class ClassPathXmlJobRegistry implements ListableJobRegistry, ApplicationContextAware, InitializingBean { +public class ClassPathXmlJobRegistry implements ListableJobRegistry, ApplicationContextAware, InitializingBean, + DisposableBean { private static Log logger = LogFactory.getLog(ClassPathXmlJobRegistry.class); @@ -51,25 +58,59 @@ public class ClassPathXmlJobRegistry implements ListableJobRegistry, Application private ListableJobRegistry jobRegistry = new MapJobRegistry(); + private Collection contexts = new HashSet(); + + /** + * A set of resources to load. Each resource should be a Spring + * configuration file which is loaded into an application context whose + * parent is the current context. In a configuration file the resources can + * be given as a pattern (e.g. + * classpath*:/config/*-job-context.xml). + * + * @param jobPaths + */ public void setJobPaths(Resource[] jobPaths) { this.jobPaths = Arrays.asList(jobPaths); } + /* + * (non-Javadoc) + * + * @see + * org.springframework.context.ApplicationContextAware#setApplicationContext + * (org.springframework.context.ApplicationContext) + */ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { parent = applicationContext; } + /* + * (non-Javadoc) + * + * @see + * org.springframework.batch.core.configuration.JobLocator#getJob(java.lang + * .String) + */ public Job getJob(String name) throws NoSuchJobException { return jobRegistry.getJob(name); } + /** + * Create all the application contexts required and set up job registry + * entries with all the instances of {@link Job} found therein. + * + * @see InitializingBean#afterPropertiesSet() + */ public void afterPropertiesSet() throws Exception { for (Resource resource : jobPaths) { ClassPathXmlApplicationContextFactory applicationContextFactory = new ClassPathXmlApplicationContextFactory(); applicationContextFactory.setPath(resource); - applicationContextFactory.setApplicationContext(parent); - ApplicationContext context = applicationContextFactory.createApplicationContext(); + if (parent != null) { + applicationContextFactory.setApplicationContext(parent); + } + ConfigurableApplicationContext context = applicationContextFactory.createApplicationContext(); + contexts.add(context); String[] names = context.getBeanNamesForType(Job.class); for (String name : names) { @@ -86,6 +127,35 @@ public class ClassPathXmlJobRegistry implements ListableJobRegistry, Application } + /** + * Close the contexts that were created in {@link #afterPropertiesSet()}. + * + * @see DisposableBean#destroy() + */ + public void destroy() throws Exception { + + try { + + for (ConfigurableApplicationContext context : contexts) { + + String[] names = context.getBeanNamesForType(Job.class); + + try { + for (String name : names) { + unregister(name); + } + } + finally { + context.close(); + } + + } + } + finally { + contexts.clear(); + } + } + public Collection getJobNames() { return jobRegistry.getJobNames(); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistryTests.java index 9f0509a5d..e0914ecbe 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/support/ClassPathXmlJobRegistryTests.java @@ -70,4 +70,18 @@ public class ClassPathXmlJobRegistryTests { registry.afterPropertiesSet(); assertEquals(2, registry.getJobNames().size()); } + + @Test + public void testDestroy() throws Exception { + + Resource[] jobPaths = new Resource[] { new ClassPathResource( + "org/springframework/batch/core/launch/support/2jobs.xml") }; + registry.setJobPaths(jobPaths); + registry.afterPropertiesSet(); + assertEquals(2, registry.getJobNames().size()); + registry.destroy(); + assertEquals(0, registry.getJobNames().size()); + + } + } diff --git a/spring-batch-samples/src/main/resources/jobs/skipSampleJob.xml b/spring-batch-samples/src/main/resources/jobs/skipSampleJob.xml index f890d4cf6..e61a6445e 100644 --- a/spring-batch-samples/src/main/resources/jobs/skipSampleJob.xml +++ b/spring-batch-samples/src/main/resources/jobs/skipSampleJob.xml @@ -88,13 +88,13 @@ - + - + - + @@ -112,12 +112,12 @@ - + - + @@ -125,7 +125,7 @@ - +