Spring Batch is a popular implementation of JSR 352. But how can it be used in a Java EE application server environment using CDI-Beans.
In my current project we are implementing a web-based application. As an addition to this I implemented a batch process. The batch should reuse business functions, that were already implemented as CDI-Beans. On the framework side I used Spring Batch, because it supports Batch JSR 352 and is used in many projects for years. The challenge was to use the existing CDI-Beans in a Spring Batch.
I found the following solution based on
Websphere Application Server 8.0 (implementing Java EE 6)
Spring Batch 3.0.6 (JSR 352 is Part of JEE 7, so I had to use an external framework like Spring Batch instead of using the batch framework from the application server)
Out of the box the solution outlined by CDISource didn´t work for me, so I tried my own implementation inspired by the code of CDISource.
With an implementation of Springs BeanFactoryPostProcessor-Interface I registered all @Named CDI-Beans in Spring. These are more beans than I really need to register, but for now it works and can be optimized in the future.
The code above searches with CDIs BeanManager for all CDI-Beans that have a name (by using @Named annotation). All found beans are registered by this name in Spring Context using the FactoryBean implementation below. The getObject() method retrieves the instance of the CDI-Bean.
The CdiBeanFactoryPostProcessor has to be executed during initialization of a Spring Batch. The convention of Spring Batch is to create a file named META-INF/batch.xml, which is automatically initialized during the start of the batch.
The batch was started by calling BatchRuntime.getJobOperator().jobOperator.start("my-batch", new Properties());
but it didn´t worked as expected. Spring Batch searched for classes with classnames myReader, myProcessor and myWriter, but not for the CDI-Beans with these names. After a lot of debugging I found the problem. During initialization of batch-job.xml another BeanFactoryPostProcessor creates beans with these names before my CdiBeanFactoryPostProcessor was executed.
The solution for this is to implement org.springframework.core.PriorityOrdered to execute the CdiBeanFactoryPostProcessor in front of all other BeanFactoryPostProcessor.
The logging worked but not the business function. The difference was that MyBusinessFunction was annotated with @ApplicationScoped while the Logger did not. So why was there no ApplicationScoped CDI-Context active. I started the batch from a thread that was associated with one.
But Spring Batch forks for every batch a separate thread, even if the batch will be executed single-threaded. One solution could be to implement a javax.batch.api.listener.StepListener and configure the batch to use this StepListener.
But wait! Spring Batch creates a new thread in a Websphere Application Server? Conforming to JEE specification this should not be done. The application server should manage resources like threads.
Spring Batch uses by default a bean named taskExecutor to manage threads. The default can be overriden by putting a file baseContext.xml in the classpath:
The value wm/batchWorkManager is the JNDI-Name of a Java EE WorkManager which has to be configured in Websphere. This WorkManager manages the threads.
Ok the thread is now managed by Websphere, but how does that help with the CDI-Context-Problem? I implemented a subclass of WorkManagerTaskExecutor overriding its execute() method. The original runnable, which will execute the batch, is wrapped by my own runnable implementation. Before executing the wrapped runnable, CDIs ContextFactory is used to create an ApplicationContext. This Context is destroyed after executing the wrapped runnable.