Spring Batch 3.0 supports writing and running batch jobs that comply with the JSR-352 specification, which is the standard for batch processing also included in JEE7. This article series focuses on three topics:
- configuration options using Spring Batch’s implementation the standard way
- integrating the possibility to run JSR-352 style jobs in your existing Spring Batch environment
- using Spring’s dependency injection functionality within JSR-352 style jobs
Today we’ll take a look at the first two topics.
The JSR-352 spec tells us that a job may be started this way:
1JobOperator jobOperator = BatchRuntime.getJobOperator(); 2Properties jobParameters = new Properties(); 3jobOperator.start("<name of job xml without suffix>", jobParameters);
where the job xml is placed in the classpath under META-INF/batch-jobs
and may look like this:
1<?xml version="1.0" encoding="UTF-8"?> 2<job id="simpleJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/javaee/jobXML_1_0.xsd"> 4 <step id="chunkStep"> 5 <chunk item-count="2"> 6 <reader ref="de.codecentric.batch.item.DummyItemReader"/> 7 <processor ref="de.codecentric.batch.item.LogItemProcessor"/> 8 <writer ref="de.codecentric.batch.item.LogItemWriter"/> 9 </chunk> 10 </step> 11</job>
Spring Batch supports this kind of execution, so somewhere while creating the JobOperator
the whole Spring Batch configuration must be set up. So, what’s happening when you call BatchRuntime.getJobOperator()
?
How the standard configuration works
The class BatchRuntime
is provided by the spec, it just uses the service loader capabilities of the JDK to identify the implementation of JobOperator
. In our case Spring Batch provides the text file META-INF/services/javax.batch.operations.JobOperator
in its spring-batch-core distribution, and its content leads us to the class that gets instantiated when we call BatchRuntime.getJobOperator()
:
1org.springframework.batch.core.jsr.launch.JsrJobOperator
The service loader mechanism instantiates this class via reflection with the no-arg constructor, which is this one:
1public JsrJobOperator() {
2 BeanFactoryLocator beanFactoryLocactor = ContextSingletonBeanFactoryLocator.getInstance();
3 BeanFactoryReference ref = beanFactoryLocactor.useBeanFactory("baseContext");
4 baseContext = (ApplicationContext) ref.getFactory();
5 baseContext.getAutowireCapableBeanFactory().autowireBeanProperties(this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);
6 if(taskExecutor == null) {
7 taskExecutor = new SimpleAsyncTaskExecutor();
8 }
9}
Okay, so we’re using the ContextSingletonBeanFactoryLocator
here to get the ApplicationContext
containing the Spring Batch configuration. First of all it means that no matter how often we call BatchRuntime.getJobOperator()
, the configuration behind it is only initialized once and kept in the ContextSingletonBeanFactoryLocator
.
So, how does the ContextSingletonBeanFactoryLocator
work?
It looks for a beanRefContext.xml
somewhere in the classpath. This ApplicationContext
xml normally doesn’t contain “normal” Spring beans but references to other xml configurations. In our case the beanRefContext.xml
from the root of the spring-batch-core jar is found. It contains one reference to the baseContext.xml
in the same location. Finally we found the location of the Spring Batch configuration used, so let’s take a look inside.
JobRepository
, JobLauncher
, a classic Spring Batch JobOperator
, JobExplorer
, DataSource
, TransactionManager
, a database initializer, JobParametersConverter
, JobRegistry
and a PropertyPlaceholderConfigurer
are configured here, everything you need to get jobs running.
What can we do to customize this configuration? At the end of baseContext.xml
the PropertyPlaceholderConfigurer
is defined:
1<bean id="placeholderProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
2 <property name="locations">
3 <list>
4 <value>classpath:batch-${ENVIRONMENT:hsql}.properties</value>
5 </list>
6 </property>
7 <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
8 <property name="ignoreResourceNotFound" value="true" />
9 <property name="ignoreUnresolvablePlaceholders" value="false" />
10 <property name="order" value="1" />
11</bean>
There are a lot of properties used in baseContext.xml
, for example for defining the database connection and so on. They are loaded from a properties file with this definition:
1classpath:batch-${ENVIRONMENT:hsql}.properties
If you don’t set an environment variable or JVM parameter named ENVIRONMENT
, the default batch-hsql.properties
is used, which gives you an in-memory HSQL database.
How to customize the standard configuration
If your customization is done with specifying connection data for your individual database or changing some other of those properties, just create a batch-{your-database}.properties
, set the properties in it, place it in the root of your classpath and set the environment variable or JVM parameter ENVIRONMENT
to {your-database}
.
If you need to change individual beans, create a baseContext.xml
with your bean definitions and place it in the root of your classpath. For example, this is necessary if you want to access resources via JNDI when running in an application server environment. DataSource
, TransactionManager
and TaskExecutor
will be different then.
Integrating into existing Spring Batch infrastructure
What do you do if you already have your custom Spring Batch infrastructure, JobRepository
, JobLauncher
, database and so on, and just want to add the possibility to start JSR-352 style jobs?
That was the challenge we were facing in our own open source project spring-boot-starter-batch-web that already handled Spring Batch xml and Spring Batch JavaConfig. We didn’t want to establish another line of configuration objects, we wanted to re-use our JobRepository
and co. And, to be honest, we wanted to get rid of those service locator magic and do normal dependency injection.
So we decided to instantiate the JsrJobOperator
ourselves and provide the necessary dependencies to it. Unfortunately at the time of writing, the JsrJobOperator
didn’t support that, because it’s not possible to set the baseContext
inside JsrJobOperator
to our own parent context. baseContext
is private, static and has no setters.
We patched the JsrJobOperator
and made it implement ApplicationContextAware
(take a look here), so when it is created now inside an existing ApplicationContext
, it’ll take that as baseContext
. There’s already a Jira issue with this subject.
Now we can re-use JobRepository
, JobExplorer
and TaskExecutor
to run JSR-352 style jobs with the JsrJobOperator
.
Conclusion
Customization is pretty easy if you know how to do it, but unfortunately the docs are lacking there a little bit. Integrating the possibility to start JSR-352 style jobs into an existing Spring Batch infrastructure can, at the moment, only be done with a little patching.
More articles
fromTobias Flohre
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Tobias Flohre
Senior Software Developer
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.