diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java index 3c050a0856bc..f0a9b66a07e4 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.java @@ -17,12 +17,19 @@ package org.springframework.boot.autoconfigure.batch; import java.util.List; +import java.util.stream.Stream; import javax.sql.DataSource; +import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.ListableJobLocator; +import org.springframework.batch.core.configuration.StepRegistry; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.support.ApplicationContextFactory; +import org.springframework.batch.core.configuration.support.AutomaticJobRegistrar; import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration; +import org.springframework.batch.core.configuration.support.DefaultJobLoader; +import org.springframework.batch.core.configuration.support.JobLoader; import org.springframework.batch.core.converter.JobParametersConverter; import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobLauncher; @@ -66,6 +73,7 @@ * @author EddĂș MelĂ©ndez * @author Kazuki Shimizu * @author Mahmoud Ben Hassine + * @author Yanming Zhou * @since 1.0.0 */ @AutoConfiguration(after = { HibernateJpaAutoConfiguration.class, TransactionAutoConfiguration.class }) @@ -162,6 +170,32 @@ protected ConfigurableConversionService getConversionService() { } + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(prefix = "spring.batch.job", name = "modular", havingValue = "true") + static class SpringBootModularBatchConfiguration { + + @Bean + JobLoader jobLoader(JobRegistry jobRegistry, ObjectProvider stepRegistry) { + DefaultJobLoader jobLoader = new DefaultJobLoader(jobRegistry); + stepRegistry.ifAvailable(jobLoader::setStepRegistry); + return jobLoader; + } + + @Bean + AutomaticJobRegistrar automaticJobRegistrar(JobLoader jobLoader, + ObjectProvider applicationContextFactories, + ObjectProvider applicationContextFactoryArrays) { + AutomaticJobRegistrar automaticJobRegistrar = new AutomaticJobRegistrar(); + automaticJobRegistrar.setJobLoader(jobLoader); + applicationContextFactories.forEach(automaticJobRegistrar::addApplicationContextFactory); + applicationContextFactoryArrays.stream() + .flatMap(Stream::of) + .forEach(automaticJobRegistrar::addApplicationContextFactory); + return automaticJobRegistrar; + } + + } + @Configuration(proxyBeanMethods = false) @Conditional(OnBatchDatasourceInitializationCondition.class) static class DataSourceInitializerConfiguration { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index be821293afaa..24af007bf664 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -501,6 +501,12 @@ "description": "Execute all Spring Batch jobs in the context on startup.", "defaultValue": true }, + { + "name": "spring.batch.job.modular", + "type": "java.lang.Boolean", + "description": "Whether the job configuration is going to be modularized like @EnableBatchProcessing(modular=true)", + "defaultValue": false + }, { "name": "spring.batch.schema", "type": "java.lang.String", diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java index 19a02f1f1d33..919599e34b23 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/batch/BatchAutoConfigurationTests.java @@ -37,13 +37,17 @@ import org.springframework.batch.core.configuration.JobFactory; import org.springframework.batch.core.configuration.JobRegistry; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.configuration.support.ApplicationContextFactory; +import org.springframework.batch.core.configuration.support.ClasspathXmlApplicationContextsFactoryBean; import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration; +import org.springframework.batch.core.configuration.support.GenericApplicationContextFactory; 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; import org.springframework.batch.core.repository.JobRepository; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -75,6 +79,8 @@ import org.springframework.context.annotation.Primary; import org.springframework.core.annotation.Order; import org.springframework.core.convert.support.ConfigurableConversionService; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; @@ -93,6 +99,7 @@ * @author Stephane Nicoll * @author Vedran Pavic * @author Kazuki Shimizu + * @author Yanming Zhou */ @ExtendWith(OutputCaptureExtension.class) class BatchAutoConfigurationTests { @@ -422,6 +429,19 @@ void conversionServiceCustomizersAreCalled() { }); } + @Test + void testModular() { + this.contextRunner.withUserConfiguration(ModularConfiguration.class, EmbeddedDataSourceConfiguration.class) + .withPropertyValues("spring.batch.job.modular=true") + .run((context) -> { + JobRegistry jobRegistry = context.getBean(JobRegistry.class); + assertThat(jobRegistry.getJobNames()).containsExactlyInAnyOrder("job", "discreteLocalJob", "simpleJob"); + context.getBean(JobLauncher.class).run(jobRegistry.getJob("discreteLocalJob"), new JobParameters()); + assertThat(context.getBean(JobRepository.class) + .getLastJobExecution("discreteLocalJob", new JobParameters())).isNotNull(); + }); + } + @Configuration(proxyBeanMethods = false) protected static class BatchDataSourceConfiguration { @@ -719,4 +739,26 @@ BatchConversionServiceCustomizer anotherBatchConversionServiceCustomizer() { } + @Configuration(proxyBeanMethods = false) + static class ModularConfiguration { + + @Bean + ApplicationContextFactory jobConfiguration() { + return new GenericApplicationContextFactory(JobConfiguration.class); + } + + @Bean + ApplicationContextFactory namedJobConfigurationWithLocalJob() { + return new GenericApplicationContextFactory(NamedJobConfigurationWithLocalJob.class); + } + + @Bean + FactoryBean applicationContextFactoryArrayFactoryBean() { + ClasspathXmlApplicationContextsFactoryBean factoryBean = new ClasspathXmlApplicationContextsFactoryBean(); + factoryBean.setResources(new Resource[] { new ClassPathResource("batch/simpleJob.xml") }); + return factoryBean; + } + + } + } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/resources/batch/simpleJob.xml b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/batch/simpleJob.xml new file mode 100644 index 000000000000..aa6f81bd8e37 --- /dev/null +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/resources/batch/simpleJob.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/batch.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/batch.adoc index 91493eefd4fc..d0a66a0c3ef1 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/batch.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/howto/batch.adoc @@ -26,7 +26,7 @@ Spring Batch auto-configuration is enabled by adding `spring-boot-starter-batch` 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` beans 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 context, set the configprop:spring.batch.job.enabled[] to `false.` +To disable running a `Job` found in the application context, set the configprop:spring.batch.job.enabled[] to `false`. See {spring-boot-autoconfigure-module-code}/batch/BatchAutoConfiguration.java[BatchAutoConfiguration] for more details. @@ -60,3 +60,45 @@ This provides only one argument to the batch job: `someParameter=someValue`. Spring Batch requires a data store for the `Job` repository. If you use Spring Boot, you must use an actual database. Note that it can be an in-memory database, see {spring-batch-docs}job.html#configuringJobRepository[Configuring a Job Repository]. + + + +[[howto.batch.modularizing-job-configuration]] +=== Modularizing Job Configuration +To modularize job configuration into multiple application contexts, set the configprop:spring.batch.job.modular[] to `true`, +and supply them in separate (child) contexts through an {spring-batch-api}/core/configuration/support/ApplicationContextFactory.html[`ApplicationContextFactory`] or array of it. + +Example for JavaConfig: +[source,java,indent=0,subs="verbatim"] +---- + @Configuration(proxyBeanMethods = false) + static class ModularBatchConfiguration { + + @Bean + ApplicationContextFactory job1Configuration() { + return new GenericApplicationContextFactory(Job1Configuration.class); + } + + @Bean + ApplicationContextFactory job2Configuration() { + return new GenericApplicationContextFactory(Job2Configuration.class); + } + + } +---- + +Example for XML: +[source,java,indent=0,subs="verbatim"] +---- + @Configuration(proxyBeanMethods = false) + static class ModularBatchConfiguration { + + @Bean + FactoryBean jobConfiguration(@Value("${jobConfiguration.resources:classpath*:batch/*.xml}") Resource[] resources) { + ClasspathXmlApplicationContextsFactoryBean factoryBean = new ClasspathXmlApplicationContextsFactoryBean(); + factoryBean.setResources(resources); + return factoryBean; + } + + } +----