Skip to content

Supports modularizing job configuration like @EnableBatchProcessing(modular=true) #36202

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 })
Expand Down Expand Up @@ -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> stepRegistry) {
DefaultJobLoader jobLoader = new DefaultJobLoader(jobRegistry);
stepRegistry.ifAvailable(jobLoader::setStepRegistry);
return jobLoader;
}

@Bean
AutomaticJobRegistrar automaticJobRegistrar(JobLoader jobLoader,
ObjectProvider<ApplicationContextFactory> applicationContextFactories,
ObjectProvider<ApplicationContextFactory[]> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -93,6 +99,7 @@
* @author Stephane Nicoll
* @author Vedran Pavic
* @author Kazuki Shimizu
* @author Yanming Zhou
*/
@ExtendWith(OutputCaptureExtension.class)
class BatchAutoConfigurationTests {
Expand Down Expand Up @@ -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 {

Expand Down Expand Up @@ -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<ApplicationContextFactory[]> applicationContextFactoryArrayFactoryBean() {
ClasspathXmlApplicationContextsFactoryBean factoryBean = new ClasspathXmlApplicationContextsFactoryBean();
factoryBean.setResources(new Resource[] { new ClassPathResource("batch/simpleJob.xml") });
return factoryBean;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/batch https://www.springframework.org/schema/batch/spring-batch.xsd">
<job id="simpleJob" xmlns="http://www.springframework.org/schema/batch">
<step id="notify">
<tasklet ref="notifyTask" />
</step>
</job>
<bean id="notifyTask" class="org.springframework.batch.core.step.tasklet.SystemCommandTasklet">
<property name="command" value="shouldnotexists.sh" />
<property name="timeout" value="1000" />
</bean>
</beans>
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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<ApplicationContextFactory[]> jobConfiguration(@Value("${jobConfiguration.resources:classpath*:batch/*.xml}") Resource[] resources) {
ClasspathXmlApplicationContextsFactoryBean factoryBean = new ClasspathXmlApplicationContextsFactoryBean();
factoryBean.setResources(resources);
return factoryBean;
}

}
----