diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java index bdb6dc0a68..3fc26c667b 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java @@ -44,10 +44,14 @@ import org.springframework.jdbc.core.JdbcOperations; import org.springframework.util.StringUtils; /** - * {@link EnableAutoConfiguration Auto-configuration} for Spring Batch. By default all - * jobs in the context will be executed on startup (disable this behaviour with - * spring.boot.exec.enabled=false). User can supply a job name to execute on - * startup with spring.batch.exec.name=.... + * {@link EnableAutoConfiguration Auto-configuration} for Spring Batch. By default a Runner + * will be created and all jobs in the context will be executed on startup. + * + * Disable this behaviour with spring.batch.job.enabled=false). + * + * Alternatively, discrete Job names to execute on startup can be supplied by the User with + * a comma-delimited list: spring.batch.job.names=job1,job2. In this case the + * Runner will first find jobs registered as Beans, then those in the existing JobRegistry. * * @author Dave Syer */ @@ -57,8 +61,8 @@ import org.springframework.util.StringUtils; @ConditionalOnBean(JobLauncher.class) public class BatchAutoConfiguration { - @Value("${spring.batch.job.name:}") - private String jobName; + @Value("${spring.batch.job.names:}") + private String jobNames; @Autowired(required = false) private JobParametersConverter jobParametersConverter; @@ -74,8 +78,8 @@ public class BatchAutoConfiguration { @ConditionalOnExpression("${spring.batch.job.enabled:true}") public JobLauncherCommandLineRunner jobLauncherCommandLineRunner() { JobLauncherCommandLineRunner runner = new JobLauncherCommandLineRunner(); - if (StringUtils.hasText(this.jobName)) { - runner.setJobName(this.jobName); + if (StringUtils.hasText(this.jobNames)) { + runner.setJobNames(this.jobNames); } return runner; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JobLauncherCommandLineRunner.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JobLauncherCommandLineRunner.java index 81ad93d6a4..9d4bb67332 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JobLauncherCommandLineRunner.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/JobLauncherCommandLineRunner.java @@ -23,6 +23,7 @@ import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.Job; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecutionException; @@ -31,6 +32,8 @@ import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.converter.DefaultJobParametersConverter; import org.springframework.batch.core.converter.JobParametersConverter; import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.launch.NoSuchJobException; +import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.context.ApplicationEventPublisher; @@ -60,16 +63,19 @@ public class JobLauncherCommandLineRunner implements CommandLineRunner, @Autowired(required = false) private JobRegistry jobRegistry; + + @Autowired + private JobRepository jobRepository; - private String jobName; + private String jobNames; @Autowired(required = false) private final Collection jobs = Collections.emptySet(); private ApplicationEventPublisher publisher; - public void setJobName(String jobName) { - this.jobName = jobName; + public void setJobNames(String jobNames) { + this.jobNames = jobNames; } @Override @@ -86,17 +92,28 @@ public class JobLauncherCommandLineRunner implements CommandLineRunner, protected void launchJobFromProperties(Properties properties) throws JobExecutionException { JobParameters jobParameters = this.converter.getJobParameters(properties); - executeRegisteredJobs(jobParameters); executeLocalJobs(jobParameters); + executeRegisteredJobs(jobParameters); } private void executeRegisteredJobs(JobParameters jobParameters) throws JobExecutionException { - if (this.jobRegistry != null && StringUtils.hasText(this.jobName)) { - Job job = this.jobRegistry.getJob(this.jobName); - JobExecution execution = this.jobLauncher.run(job, jobParameters); - if (this.publisher != null) { - this.publisher.publishEvent(new JobExecutionEvent(execution)); + if (this.jobRegistry != null && StringUtils.hasText(this.jobNames)) { + String[] jobsToRun = this.jobNames.split(","); + for (String jobName : jobsToRun) { + try { + Job job = this.jobRegistry.getJob(jobName); + JobExecution previousExecution = jobRepository.getLastJobExecution(jobName, jobParameters); + if (previousExecution == null || previousExecution.getStatus() != BatchStatus.COMPLETED) { + JobExecution execution = this.jobLauncher.run(job, jobParameters); + if (this.publisher != null) { + this.publisher.publishEvent(new JobExecutionEvent(execution)); + } + } + } catch (NoSuchJobException nsje) { + logger.debug("No job found in registry for job name: " + jobName); + continue; + } } } } @@ -104,8 +121,9 @@ public class JobLauncherCommandLineRunner implements CommandLineRunner, private void executeLocalJobs(JobParameters jobParameters) throws JobExecutionException { for (Job job : this.jobs) { - if (StringUtils.hasText(this.jobName)) { - if (!PatternMatchUtils.simpleMatch(this.jobName, job.getName())) { + if (StringUtils.hasText(this.jobNames)) { + String[] jobsToRun = this.jobNames.split(","); + if (!PatternMatchUtils.simpleMatch(jobsToRun, job.getName())) { logger.debug("Skipped job: " + job.getName()); continue; } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java index 76bc822c31..5137e8f27a 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java @@ -32,7 +32,9 @@ import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecutionException; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.Step; +import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.job.AbstractJob; import org.springframework.batch.core.launch.JobLauncher; @@ -112,6 +114,36 @@ public class BatchAutoConfigurationTests { assertNotNull(this.context.getBean(JobRepository.class).getLastJobExecution( "job", new JobParameters())); } + + @Test + public void testDefinesAndLaunchesNamedJob() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.batch.job.names:discreteRegisteredJob"); + this.context.register(NamedJobConfigurationWithRegisteredJob.class, BatchAutoConfiguration.class, + EmbeddedDataSourceConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + assertNotNull(this.context.getBean(JobLauncher.class)); + this.context.getBean(JobLauncherCommandLineRunner.class).run(); + assertNotNull(this.context.getBean(JobRepository.class).getLastJobExecution( + "discreteRegisteredJob", new JobParameters())); + } + + @Test + public void testDefinesAndLaunchesLocalJob() throws Exception { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.batch.job.names:discreteLocalJob"); + this.context.register(NamedJobConfigurationWithLocalJob.class, BatchAutoConfiguration.class, + EmbeddedDataSourceConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + assertNotNull(this.context.getBean(JobLauncher.class)); + this.context.getBean(JobLauncherCommandLineRunner.class).run(); + assertNotNull(this.context.getBean(JobRepository.class).getLastJobExecution( + "discreteLocalJob", new JobParameters())); + } @Test public void testDisableLaunchesJob() throws Exception { @@ -170,6 +202,77 @@ public class BatchAutoConfigurationTests { protected static class TestConfiguration { } + @EnableBatchProcessing + protected static class NamedJobConfigurationWithRegisteredJob { + @Autowired + private JobRegistry jobRegistry; + + @Autowired + private JobRepository jobRepository; + + @Bean + public JobRegistryBeanPostProcessor registryProcessor() { + JobRegistryBeanPostProcessor processor = new JobRegistryBeanPostProcessor(); + processor.setJobRegistry(jobRegistry); + return processor; + } + + @Bean + public Job discreteJob() { + AbstractJob job = new AbstractJob("discreteRegisteredJob") { + + @Override + public Collection getStepNames() { + return Collections.emptySet(); + } + + @Override + public Step getStep(String stepName) { + return null; + } + + @Override + protected void doExecute(JobExecution execution) + throws JobExecutionException { + execution.setStatus(BatchStatus.COMPLETED); + } + }; + job.setJobRepository(this.jobRepository); + return job; + } + } + + @EnableBatchProcessing + protected static class NamedJobConfigurationWithLocalJob { + + @Autowired + private JobRepository jobRepository; + + @Bean + public Job discreteJob() { + AbstractJob job = new AbstractJob("discreteLocalJob") { + + @Override + public Collection getStepNames() { + return Collections.emptySet(); + } + + @Override + public Step getStep(String stepName) { + return null; + } + + @Override + protected void doExecute(JobExecution execution) + throws JobExecutionException { + execution.setStatus(BatchStatus.COMPLETED); + } + }; + job.setJobRepository(this.jobRepository); + return job; + } + } + @EnableBatchProcessing protected static class JobConfiguration { @Autowired