1
votes

I'm trying to implement sample batch application using JSR-352 API and Spring Batch 3.0.4 as implementation.

Batch job execution fails during initialization phase on error while creating bean with name 'batchPropertyPostProcessor':

Exception in thread "main" javax.batch.operations.JobStartException: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'batchPropertyPostProcessor': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springframework.batch.core.jsr.launch.support.BatchPropertyBeanPostProcessor.setBatchPropertyContext(org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext); nested exception is org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [dataListingStepListener] for bean with name 'scopedTarget.dataListingStepListener' defined in null; nested exception is java.lang.ClassNotFoundException: dataListingStepListener
    at org.springframework.batch.core.jsr.launch.JsrJobOperator.start(JsrJobOperator.java:637)
    at x98.BatchRunner.main(BatchRunner.java:18)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'batchPropertyPostProcessor': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springframework.batch.core.jsr.launch.support.BatchPropertyBeanPostProcessor.setBatchPropertyContext(org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext); nested exception is org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [dataListingStepListener] for bean with name 'scopedTarget.dataListingStepListener' defined in null; nested exception is java.lang.ClassNotFoundException: dataListingStepListener
    at org.springframework.batch.core.jsr.configuration.support.SpringAutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(SpringAutowiredAnnotationBeanPostProcessor.java:262)
    at org.springframework.batch.core.jsr.configuration.support.JsrAutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(JsrAutowiredAnnotationBeanPostProcessor.java:30)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1185)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:198)
    at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:232)
    at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:618)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:467)
    at org.springframework.batch.core.jsr.launch.JsrJobOperator.start(JsrJobOperator.java:635)
    ... 1 more
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springframework.batch.core.jsr.launch.support.BatchPropertyBeanPostProcessor.setBatchPropertyContext(org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext); nested exception is org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [dataListingStepListener] for bean with name 'scopedTarget.dataListingStepListener' defined in null; nested exception is java.lang.ClassNotFoundException: dataListingStepListener
    at org.springframework.batch.core.jsr.configuration.support.SpringAutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(SpringAutowiredAnnotationBeanPostProcessor.java:575)
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
    at org.springframework.batch.core.jsr.configuration.support.SpringAutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(SpringAutowiredAnnotationBeanPostProcessor.java:259)
    ... 13 more
Caused by: org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [dataListingStepListener] for bean with name 'scopedTarget.dataListingStepListener' defined in null; nested exception is java.lang.ClassNotFoundException: dataListingStepListener
    at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1325)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.predictBeanType(AbstractAutowireCapableBeanFactory.java:594)
    at org.springframework.beans.factory.support.AbstractBeanFactory.isTypeMatch(AbstractBeanFactory.java:526)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doGetBeanNamesForType(DefaultListableBeanFactory.java:387)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:354)
    at org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors(BeanFactoryUtils.java:187)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1002)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:960)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:858)
    at org.springframework.batch.core.jsr.configuration.support.SpringAutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(SpringAutowiredAnnotationBeanPostProcessor.java:532)
    ... 15 more
Caused by: java.lang.ClassNotFoundException: dataListingStepListener
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at org.springframework.util.ClassUtils.forName(ClassUtils.java:247)
    at org.springframework.beans.factory.support.AbstractBeanDefinition.resolveBeanClass(AbstractBeanDefinition.java:395)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doResolveBeanClass(AbstractBeanFactory.java:1346)
    at org.springframework.beans.factory.support.AbstractBeanFactory.resolveBeanClass(AbstractBeanFactory.java:1317)
    ... 24 more

My batch runner:

public class BatchRunner {

    public static void main(String[] args) {

        System.setProperty("JSR-352-BASE-CONTEXT", "x98_batch_local.xml");

        Properties jobParameters = new Properties();
        jobParameters.put("message", "Hello!");

        JobOperator jobOperator = BatchRuntime.getJobOperator();
        jobOperator.start("x98SampleJob", jobParameters);

    }
}

My sampleJob definition:

<?xml version="1.0" encoding="UTF-8"?>
<job id="x98SampleJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/jobXML_1_0.xsd"
    version="1.0">

    <step id="simple" next="dataListing">
        <batchlet ref="simpleBatchlet">
            <properties>
                <property name="message" value="#{jobParameters['message']}" />
            </properties>
        </batchlet>
    </step>

    <step id="dataListing">
        <listeners>
            <listener ref="dataListingStepListener"/>
        </listeners>
        <chunk item-count="3">
            <reader ref="dataListingItemReader" />
            <processor ref="dataListingItemProcessor"/>
            <writer ref="dataListingItemWriter"/>
        </chunk>
    </step>
</job>  

My Spring bean configuration for reader, writer, processor and listener

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd">

    <import resource="classpath*:x98_services.xml"/>

    <bean name="simpleBatchlet" class="x98.batch.SimpleBatchlet"/>

    <bean name="dataListingItemProcessor" class="x98.batch.DataListingItemProcessor"/>

    <bean name="dataListingItemReader" class="x98.batch.DataListingItemReader">
        <property name="tx98DatasService" ref="tx98DatasService"/>
        <property name="tx98StructureService" ref="tx98StructureService"/>
    </bean>

    <bean name="dataListingItemWriter" class="x98.batch.DataListingItemWriter"/>

    <bean name="dataListingStepListener" class="x98.batch.DataListingStepListener">
        <property name="tx98StatusService" ref="tx98StatusService"/>
    </bean>
</beans>

Reader, writer, and processor are instantiated by Spring and used properly in job run. When I added listener to job definition it stopped to work.

I debugged the code and I saw that dataListingStepListener bean was initialized in the Spring context. I don't understand why reference to listener (<listener ref="dataListingStepListener"/>) is not recognized as Spring bean and Spring is trying instead load class named "dataListingStepListener".

Is my configuration OK? Am I doing something wrong?

1
I don't see anything obvious wrong and I know we have unit tests that use listeners (e.g. github.com/spring-projects/spring-batch/blob/master/…). If you can put a gist together I can grab, I'd be happy to take a deeper look.Michael Minella
Hi Michael, here is gist - gist.github.com/pezz78/732108fdf290e8397247 Thankspeter

1 Answers

1
votes

After reviewing the Gist, there are a couple notes:

Job specific beans must be in the child context
Spring Batch's JSR-352 context management works via a parent/child relationship. The parent context is defined by the baseContext.xml or can be overriden as you have done via the system property JSR-352-BASE-CONTEXT. That context is intended to be used only for infrastructure beans and is not processed by the JSR-352 post processors for things like property injection. You said "When I added listener to job definition it stopped to work." If you looked closely, while the job ran, your property wasn't injected into SimpleBatchlet (message was null).

The reason for this is if you want to run multiple jobs, you shouldn't need to create multiple JobRepository instances, multiple connection pools, etc. so we only bootstrap the parent context once. Since we only bootstrap it once, we don't have the values for things like job properties to inject at that time (and they may change from job to job).

So with that, all job specific beans should be in the child context.

Defining a child context for JSR-352
JSR-352 provides three different ways of handling bean creation:

  1. Inline - Provide the fully qualified class name in the ref attribute of the artifact definition in the job XML and it will be created using a no-arg constructor.
  2. batch.xml - The batch.xml file provides a way to simplify the above concept by providing aliases for class names.
  3. Implementation specific DI (Spring DI in this case) - Spring Batch's implementation of JSR-352 provides the ability to use Spring's DI facilities for bean creation.

With that, we turn the control over to you as to how bean instances are created. By default, in Spring, beans are singletons that are created on startup. However, Spring Batch provides scopes (step or job) to allow for late binding of properties. If you want property injection, a bean must be defined as step scope (SimpleBatchlet was not which is why the message is null the first run).

So how do you achieve the separation of concerns between the Spring DI and standards based JSR-352 when defining your job's context? Simple. Import the job XML into the Spring XML and pass the Spring XML name to the JobOperator to be launched. In your case, instead of:

public static void main(String[] args) {

    System.setProperty("JSR-352-BASE-CONTEXT", "x98_batch_local.xml");

    Properties jobParameters = new Properties();
    jobParameters.put("message", "Hello!");

    JobOperator jobOperator = BatchRuntime.getJobOperator();
    jobOperator.start("x98SampleJob", jobParameters);

}

Add the following to your x98_batch_local.xml:

<import resource="classpath*:x98SampleJob.xml"/>

And then execute your job via:

public static void main(String[] args) {

    Properties jobParameters = new Properties();
    jobParameters.put("message", "Hello!");

    JobOperator jobOperator = BatchRuntime.getJobOperator();
    jobOperator.start("x98_batch_local.xml", jobParameters);

}

The above tweak and setting your SimpleBatchlet to be step scoped will cause your job to run as expected.

NOTE: As a result of this question, I've created issue BATCH-2388 to better document the context considerations discussed above.