parent
fabe0637cd
commit
55d6a87fef
|
|
@ -45,15 +45,14 @@ import org.springframework.jdbc.datasource.init.DatabasePopulator;
|
|||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@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.
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for Spring Batch. If a single job is
|
||||
* found in the context, it will be executed on startup.
|
||||
* <p>
|
||||
* Disable this behavior with {@literal spring.batch.job.enabled=false}).
|
||||
* <p>
|
||||
* Alternatively, discrete Job names to execute on startup can be supplied by the User
|
||||
* with a comma-delimited list: {@literal spring.batch.job.names=job1,job2}. In this case
|
||||
* the Runner will first find jobs registered as Beans, then those in the existing
|
||||
* JobRegistry.
|
||||
* If multiple jobs are found, a job name to execute on startup can be supplied by the
|
||||
* User with : {@literal spring.batch.job.name=job1}. In this case the Runner will first
|
||||
* find jobs registered as Beans, then those in the existing JobRegistry.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Eddú Meléndez
|
||||
|
|
@ -74,9 +73,9 @@ public class BatchAutoConfiguration {
|
|||
public JobLauncherApplicationRunner jobLauncherApplicationRunner(JobLauncher jobLauncher, JobExplorer jobExplorer,
|
||||
JobRepository jobRepository, BatchProperties properties) {
|
||||
JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(jobLauncher, jobExplorer, jobRepository);
|
||||
String jobNames = properties.getJob().getNames();
|
||||
String jobNames = properties.getJob().getName();
|
||||
if (StringUtils.hasText(jobNames)) {
|
||||
runner.setJobNames(jobNames);
|
||||
runner.setJobName(jobNames);
|
||||
}
|
||||
return runner;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,17 +46,17 @@ public class BatchProperties {
|
|||
public static class Job {
|
||||
|
||||
/**
|
||||
* Comma-separated list of job names to execute on startup (for instance,
|
||||
* 'job1,job2'). By default, all Jobs found in the context are executed.
|
||||
* Job name to execute on startup. Must be specified if multiple Jobs are found in
|
||||
* the context.
|
||||
*/
|
||||
private String names = "";
|
||||
private String name = "";
|
||||
|
||||
public String getNames() {
|
||||
return this.names;
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setNames(String names) {
|
||||
this.names = names;
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import java.util.LinkedHashMap;
|
|||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
|
|
@ -53,7 +54,6 @@ import org.springframework.context.ApplicationEventPublisherAware;
|
|||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
|
@ -86,7 +86,7 @@ public class JobLauncherApplicationRunner implements ApplicationRunner, Ordered,
|
|||
|
||||
private JobRegistry jobRegistry;
|
||||
|
||||
private String jobNames;
|
||||
private String jobName;
|
||||
|
||||
private Collection<Job> jobs = Collections.emptySet();
|
||||
|
||||
|
|
@ -110,6 +110,13 @@ public class JobLauncherApplicationRunner implements ApplicationRunner, Ordered,
|
|||
this.jobRepository = jobRepository;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void validate() {
|
||||
if (this.jobs.size() > 1 && !StringUtils.hasText(this.jobName)) {
|
||||
throw new IllegalArgumentException("Job name must be specified in case of multiple jobs");
|
||||
}
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
|
@ -129,8 +136,8 @@ public class JobLauncherApplicationRunner implements ApplicationRunner, Ordered,
|
|||
this.jobRegistry = jobRegistry;
|
||||
}
|
||||
|
||||
public void setJobNames(String jobNames) {
|
||||
this.jobNames = jobNames;
|
||||
public void setJobName(String jobName) {
|
||||
this.jobName = jobName;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
|
|
@ -162,9 +169,8 @@ public class JobLauncherApplicationRunner implements ApplicationRunner, Ordered,
|
|||
|
||||
private void executeLocalJobs(JobParameters jobParameters) throws JobExecutionException {
|
||||
for (Job job : this.jobs) {
|
||||
if (StringUtils.hasText(this.jobNames)) {
|
||||
String[] jobsToRun = this.jobNames.split(",");
|
||||
if (!PatternMatchUtils.simpleMatch(jobsToRun, job.getName())) {
|
||||
if (StringUtils.hasText(this.jobName)) {
|
||||
if (!this.jobName.equals(job.getName())) {
|
||||
logger.debug(LogMessage.format("Skipped job: %s", job.getName()));
|
||||
continue;
|
||||
}
|
||||
|
|
@ -174,19 +180,15 @@ public class JobLauncherApplicationRunner implements ApplicationRunner, Ordered,
|
|||
}
|
||||
|
||||
private void executeRegisteredJobs(JobParameters jobParameters) throws JobExecutionException {
|
||||
if (this.jobRegistry != null && StringUtils.hasText(this.jobNames)) {
|
||||
String[] jobsToRun = this.jobNames.split(",");
|
||||
for (String jobName : jobsToRun) {
|
||||
try {
|
||||
Job job = this.jobRegistry.getJob(jobName);
|
||||
if (this.jobs.contains(job)) {
|
||||
continue;
|
||||
}
|
||||
if (this.jobRegistry != null && StringUtils.hasText(this.jobName)) {
|
||||
try {
|
||||
Job job = this.jobRegistry.getJob(this.jobName);
|
||||
if (!this.jobs.contains(job)) {
|
||||
execute(job, jobParameters);
|
||||
}
|
||||
catch (NoSuchJobException ex) {
|
||||
logger.debug(LogMessage.format("No job found in registry for job name: %s", jobName));
|
||||
}
|
||||
}
|
||||
catch (NoSuchJobException ex) {
|
||||
logger.debug(LogMessage.format("No job found in registry for job name: %s", this.jobName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import javax.sql.DataSource;
|
|||
import jakarta.persistence.EntityManagerFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.batch.core.BatchStatus;
|
||||
import org.springframework.batch.core.Job;
|
||||
|
|
@ -31,6 +32,8 @@ import org.springframework.batch.core.JobExecution;
|
|||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.JobParametersBuilder;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.configuration.DuplicateJobException;
|
||||
import org.springframework.batch.core.configuration.JobFactory;
|
||||
import org.springframework.batch.core.configuration.JobRegistry;
|
||||
import org.springframework.batch.core.configuration.annotation.BatchConfigurer;
|
||||
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
|
||||
|
|
@ -39,7 +42,9 @@ import org.springframework.batch.core.explore.JobExplorer;
|
|||
import org.springframework.batch.core.job.AbstractJob;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.DefaultApplicationArguments;
|
||||
|
|
@ -60,6 +65,7 @@ import org.springframework.boot.sql.init.DatabaseInitializationSettings;
|
|||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.system.CapturedOutput;
|
||||
import org.springframework.boot.test.system.OutputCaptureExtension;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
|
@ -138,11 +144,11 @@ class BatchAutoConfigurationTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testDefinesAndLaunchesNamedJob() {
|
||||
void testDefinesAndLaunchesNamedRegisteredJob() {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(NamedJobConfigurationWithRegisteredJob.class,
|
||||
EmbeddedDataSourceConfiguration.class)
|
||||
.withPropertyValues("spring.batch.job.names:discreteRegisteredJob").run((context) -> {
|
||||
.withPropertyValues("spring.batch.job.name:discreteRegisteredJob").run((context) -> {
|
||||
assertThat(context).hasSingleBean(JobLauncher.class);
|
||||
context.getBean(JobLauncherApplicationRunner.class).run();
|
||||
assertThat(context.getBean(JobRepository.class).getLastJobExecution("discreteRegisteredJob",
|
||||
|
|
@ -150,11 +156,46 @@ class BatchAutoConfigurationTests {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRegisteredAndLocalJob() {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(NamedJobConfigurationWithRegisteredAndLocalJob.class,
|
||||
EmbeddedDataSourceConfiguration.class)
|
||||
.withPropertyValues("spring.batch.job.name:discreteRegisteredJob").run((context) -> {
|
||||
assertThat(context).hasSingleBean(JobLauncher.class);
|
||||
context.getBean(JobLauncherApplicationRunner.class).run();
|
||||
assertThat(context.getBean(JobRepository.class)
|
||||
.getLastJobExecution("discreteRegisteredJob", new JobParameters()).getStatus())
|
||||
.isEqualTo(BatchStatus.COMPLETED);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefinesAndLaunchesLocalJob() {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(NamedJobConfigurationWithLocalJob.class, EmbeddedDataSourceConfiguration.class)
|
||||
.withPropertyValues("spring.batch.job.names:discreteLocalJob").run((context) -> {
|
||||
.withPropertyValues("spring.batch.job.name:discreteLocalJob").run((context) -> {
|
||||
assertThat(context).hasSingleBean(JobLauncher.class);
|
||||
context.getBean(JobLauncherApplicationRunner.class).run();
|
||||
assertThat(context.getBean(JobRepository.class).getLastJobExecution("discreteLocalJob",
|
||||
new JobParameters())).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipleJobsAndNoJobName() {
|
||||
this.contextRunner.withUserConfiguration(MultipleJobConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||
.run((context) -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure().getCause().getMessage())
|
||||
.contains("Job name must be specified in case of multiple jobs");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMultipleJobsAndJobName() {
|
||||
this.contextRunner.withUserConfiguration(MultipleJobConfiguration.class, EmbeddedDataSourceConfiguration.class)
|
||||
.withPropertyValues("spring.batch.job.name:discreteLocalJob").run((context) -> {
|
||||
assertThat(context).hasSingleBean(JobLauncher.class);
|
||||
context.getBean(JobLauncherApplicationRunner.class).run();
|
||||
assertThat(context.getBean(JobRepository.class).getLastJobExecution("discreteLocalJob",
|
||||
|
|
@ -395,18 +436,15 @@ class BatchAutoConfigurationTests {
|
|||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableBatchProcessing
|
||||
static class NamedJobConfigurationWithRegisteredJob {
|
||||
|
||||
@Autowired
|
||||
private JobRegistry jobRegistry;
|
||||
static class NamedJobConfigurationWithRegisteredAndLocalJob {
|
||||
|
||||
@Autowired
|
||||
private JobRepository jobRepository;
|
||||
|
||||
@Bean
|
||||
JobRegistryBeanPostProcessor registryProcessor() {
|
||||
static JobRegistryBeanPostProcessor registryProcessor(JobRegistry jobRegistry) {
|
||||
JobRegistryBeanPostProcessor processor = new JobRegistryBeanPostProcessor();
|
||||
processor.setJobRegistry(this.jobRegistry);
|
||||
processor.setJobRegistry(jobRegistry);
|
||||
return processor;
|
||||
}
|
||||
|
||||
|
|
@ -414,6 +452,8 @@ class BatchAutoConfigurationTests {
|
|||
Job discreteJob() {
|
||||
AbstractJob job = new AbstractJob("discreteRegisteredJob") {
|
||||
|
||||
private static int count = 0;
|
||||
|
||||
@Override
|
||||
public Collection<String> getStepNames() {
|
||||
return Collections.emptySet();
|
||||
|
|
@ -426,7 +466,13 @@ class BatchAutoConfigurationTests {
|
|||
|
||||
@Override
|
||||
protected void doExecute(JobExecution execution) {
|
||||
execution.setStatus(BatchStatus.COMPLETED);
|
||||
if (count == 0) {
|
||||
execution.setStatus(BatchStatus.COMPLETED);
|
||||
}
|
||||
else {
|
||||
execution.setStatus(BatchStatus.FAILED);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
};
|
||||
job.setJobRepository(this.jobRepository);
|
||||
|
|
@ -435,6 +481,75 @@ class BatchAutoConfigurationTests {
|
|||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableBatchProcessing
|
||||
static class NamedJobConfigurationWithRegisteredJob {
|
||||
|
||||
@Bean
|
||||
static BeanPostProcessor registryProcessor(ApplicationContext applicationContext) {
|
||||
return new NamedJobJobRegistryBeanPostProcessor(applicationContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class NamedJobJobRegistryBeanPostProcessor implements BeanPostProcessor {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
NamedJobJobRegistryBeanPostProcessor(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
if (bean instanceof JobRegistry jobRegistry) {
|
||||
try {
|
||||
jobRegistry.register(getJobFactory());
|
||||
}
|
||||
catch (DuplicateJobException ex) {
|
||||
}
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
private JobFactory getJobFactory() {
|
||||
JobRepository jobRepository = this.applicationContext.getBean(JobRepository.class);
|
||||
return new JobFactory() {
|
||||
|
||||
@Override
|
||||
public Job createJob() {
|
||||
AbstractJob job = new AbstractJob("discreteRegisteredJob") {
|
||||
|
||||
@Override
|
||||
public Collection<String> getStepNames() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Step getStep(String stepName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doExecute(JobExecution execution) {
|
||||
execution.setStatus(BatchStatus.COMPLETED);
|
||||
}
|
||||
|
||||
};
|
||||
job.setJobRepository(jobRepository);
|
||||
return job;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getJobName() {
|
||||
return "discreteRegisteredJob";
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableBatchProcessing
|
||||
static class NamedJobConfigurationWithLocalJob {
|
||||
|
|
@ -467,6 +582,43 @@ class BatchAutoConfigurationTests {
|
|||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableBatchProcessing
|
||||
static class MultipleJobConfiguration {
|
||||
|
||||
@Autowired
|
||||
private JobRepository jobRepository;
|
||||
|
||||
@Bean
|
||||
Job discreteJob() {
|
||||
AbstractJob job = new AbstractJob("discreteLocalJob") {
|
||||
|
||||
@Override
|
||||
public Collection<String> getStepNames() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Step getStep(String stepName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doExecute(JobExecution execution) {
|
||||
execution.setStatus(BatchStatus.COMPLETED);
|
||||
}
|
||||
};
|
||||
job.setJobRepository(this.jobRepository);
|
||||
return job;
|
||||
}
|
||||
|
||||
@Bean
|
||||
Job job2() {
|
||||
return Mockito.mock(Job.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableBatchProcessing
|
||||
static class JobConfiguration {
|
||||
|
|
|
|||
|
|
@ -22,8 +22,10 @@ For more info about Spring Batch, see the {spring-batch}[Spring Batch project pa
|
|||
=== Running Spring Batch Jobs on Startup
|
||||
Spring Batch auto-configuration is enabled by adding `@EnableBatchProcessing` to one of your `@Configuration` classes.
|
||||
|
||||
By default, it executes *all* `Jobs` in the application context on startup (see {spring-boot-autoconfigure-module-code}/batch/JobLauncherApplicationRunner.java[`JobLauncherApplicationRunner`] for details).
|
||||
You can narrow down to a specific job or jobs by specifying `spring.batch.job.names` (which takes a comma-separated list of job name patterns).
|
||||
If a single `Job` is found in the application context, it is executed on startup (see {spring-boot-autoconfigure-module-code}/batch/JobLauncherApplicationRunner.java[`JobLauncherApplicationRunner`] for details).
|
||||
If multiple `Job`s are found, the job that should be executed must be specified using configprop:spring.batch.job.name[].
|
||||
|
||||
To disable running a `Job` found in the application content, set the configprop:spring.batch.job.enabled[] to `false.`
|
||||
|
||||
See {spring-boot-autoconfigure-module-code}/batch/BatchAutoConfiguration.java[BatchAutoConfiguration] and {spring-batch-api}/core/configuration/annotation/EnableBatchProcessing.html[@EnableBatchProcessing] for more details.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue