Writing JSR-352 style jobs with Spring Batch Part 1: Configuration options - ...

:

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:

JobOperator jobOperator = BatchRuntime.getJobOperator();
Properties jobParameters = new Properties();
jobOperator.start("<name of job xml without suffix>", jobParameters);

JobOperator jobOperator = BatchRuntime.getJobOperator(); Properties jobParameters = new Properties(); jobOperator.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:

<?xml version="1.0" encoding="UTF-8"?>
<job id="simpleJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/javaee/jobXML_1_0.xsd">
    <step id="chunkStep">
        <chunk item-count="2">
            <reader ref="de.codecentric.batch.item.DummyItemReader"/>
            <processor ref="de.codecentric.batch.item.LogItemProcessor"/>
            <writer ref="de.codecentric.batch.item.LogItemWriter"/>
        </chunk>
    </step>
</job>

<?xml version="1.0" encoding="UTF-8"?> <job id="simpleJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://www.oracle.com/webfolder/technetwork/jsc/xml/ns/javaee/jobXML_1_0.xsd"> <step id="chunkStep"> <chunk item-count="2"> <reader ref="de.codecentric.batch.item.DummyItemReader"/> <processor ref="de.codecentric.batch.item.LogItemProcessor"/> <writer ref="de.codecentric.batch.item.LogItemWriter"/> </chunk> </step> </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():

org.springframework.batch.core.jsr.launch.JsrJobOperator

org.springframework.batch.core.jsr.launch.JsrJobOperator

The service loader mechanism instantiates this class via reflection with the no-arg constructor, which is this one:

public JsrJobOperator() {
    BeanFactoryLocator beanFactoryLocactor = ContextSingletonBeanFactoryLocator.getInstance();
    BeanFactoryReference ref = beanFactoryLocactor.useBeanFactory("baseContext");
    baseContext = (ApplicationContext) ref.getFactory();
    baseContext.getAutowireCapableBeanFactory().autowireBeanProperties(this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);
    if(taskExecutor == null) {
        taskExecutor = new SimpleAsyncTaskExecutor();
    }
}

public JsrJobOperator() { BeanFactoryLocator beanFactoryLocactor = ContextSingletonBeanFactoryLocator.getInstance(); BeanFactoryReference ref = beanFactoryLocactor.useBeanFactory("baseContext"); baseContext = (ApplicationContext) ref.getFactory(); baseContext.getAutowireCapableBeanFactory().autowireBeanProperties(this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false); if(taskExecutor == null) { taskExecutor = new SimpleAsyncTaskExecutor(); } }

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:

<bean id="placeholderProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
        <list>
            <value>classpath:batch-${ENVIRONMENT:hsql}.properties</value>
        </list>
    </property>
    <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
    <property name="ignoreResourceNotFound" value="true" />
    <property name="ignoreUnresolvablePlaceholders" value="false" />
    <property name="order" value="1" />
</bean>

<bean id="placeholderProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:batch-${ENVIRONMENT:hsql}.properties</value> </list> </property> <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" /> <property name="ignoreResourceNotFound" value="true" /> <property name="ignoreUnresolvablePlaceholders" value="false" /> <property name="order" value="1" /> </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:

classpath:batch-${ENVIRONMENT:hsql}.properties

classpath: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.