3
votes

I have a problem defining a java-based job configuration in Spring Batch containing a conditional flow using steps in @JobScope, i.e. injecting a job parameter into the step at job execution time.

My Setup:

  • Spring Batch 3.0.4
  • Spring Batch Admin 2.0.0.M1 (providing jobRepository, transactionManager, jobScope, stepScope beans during runtime)

Assuming I have a simple job containing two steps step1 and step2, then defining and executing the job configuration without conditional flow is working:

@Configuration
public class SimpleJobConfiguration {

@Bean
@JobScope
Step1Tasklet step1Tasklet(@Value("#{jobParameters['condition']}") String condition) {
    Step1Tasklet tasklet = new Step1Tasklet(condition);
    return tasklet;
}

// reader, writer ommitted

@Bean
@JobScope
public Step step1(@Value("#{jobParameters['condition']}") String condition) {
    TaskletStep step = stepBuilderFactory().get(STEP_NAME_1)
            .tasklet(step1Tasklet(condition))
            .build();
    return step;
}

@Bean
@JobScope
public Step step2() {
    TaskletStep step = stepBuilderFactory().get(STEP_NAME_2)
            .<String, String>chunk(10)
            .reader(reader(null))
            .writer(writer())
            .allowStartIfComplete(true)
            .build();
    return step;
}

@Bean
public Job simpleJob() {
    Job job = jobBuilderFactory().get(JOB_NAME)
            .start(step1(null))
            .next(step2())
            .build();
    return job;
}
  • When deploying the job configuration, Spring Batch is initializing my Job bean without any exceptions. It does not initialize my steps - which makes sense because they are in Scope "Job".
  • When launching an execution for this job configuration, Spring Batch is initializing both steps, injecting the job parameters as expected.
  • The job is executed as expected, first step1 then step2.

Now I want to add a simple conditional logic for my Job:

"IF (step1.exitStatus == "OK") THEN execute step2 ELSE finish job"

To achieve that I have defined a decider bean (implements JobExecutionDecider) and modified my Job definition:

@Bean
public SimpleStepDecider decider() {
    SimpleStepDecider decider = new SimpleStepDecider(); 
    return decider;
}

@Bean
// DOES NOT WORK; NEEDS TO BE FIXED!
public Job simpleJob() {
    Job job = jobBuilderFactory().get(JOB_NAME)
            .start(step1(null))
            .next(decider())
            .on("OK")
            .to(step2())
            .end()
            .build();
    return job;
}

Using this I got the following exception during deployment(!) of the job configuration:

14:28:07,299 ERROR batch.local-startStop-1 context.ContextLoader:331 - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'simpleJob' defined in com.foo.bar.SimpleJobConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.core.Job]: Factory method 'simpleJob' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.step1': Scope 'job' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No context holder available for job scope
        at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:599)
...
        at org.springframework.batch.core.configuration.support.GenericApplicationContextFactory$ResourceXmlApplicationContext.<init>(GenericApplicationContextFactory.java:161)
...
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.core.Job]: Factory method 'simpleJob' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.step1': Scope 'job' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No context holder available for job scope
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:189)
        at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:588)
        ... 39 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.step1': Scope 'job' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No context holder available for job scope
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:352)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
        at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:187)
        at com.sun.proxy.$Proxy33.getName(Unknown Source)
        at org.springframework.batch.core.job.builder.FlowBuilder.createState(FlowBuilder.java:282)
        at org.springframework.batch.core.job.builder.FlowBuilder.doStart(FlowBuilder.java:265)
        at org.springframework.batch.core.job.builder.FlowBuilder.start(FlowBuilder.java:122)
        at org.springframework.batch.core.job.builder.JobFlowBuilder.<init>(JobFlowBuilder.java:39)
        at org.springframework.batch.core.job.builder.SimpleJobBuilder.next(SimpleJobBuilder.java:133)
        at com.foo.bar.SimpleJobConfiguration.simpleJob(SimpleJobConfiguration.java:145)
        ... 40 more
Caused by: java.lang.IllegalStateException: No context holder available for job scope
        at org.springframework.batch.core.scope.JobScope.getContext(JobScope.java:153)
        at org.springframework.batch.core.scope.JobScope.get(JobScope.java:92)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337)
        ... 60 more

I tried out:

  • moving step beans to "normal" scope
  • Using step execution listener instead of decider

But no luck at all...

Funny enough, i was able to define the same job using xml-based configuration, and it works as expected (decider is using value of job parameter 'condition' as exit status code; I tested both "OK" and "NOTOK" and they work as expected):

<bean id="reader" class="com.foo.bar.MyReader" scope="job">
</bean>

<bean id="writer" class="com.foo.bar.MyWriter" />

<bean id="decider" class="com.foo.bar.SimpleStepDecider" />

<bean id="step1Tasklet" class="com.foo.bar.Step1Tasklet" scope="job">
    <constructor-arg value="#{jobParameters['condition']}" />
</bean>

<batch:job id="simpleJob" restartable="true">
    <batch:step id="step1" next="decision" allow-start-if-complete="true">
        <batch:tasklet ref="step1Tasklet" />
    </batch:step>
    <batch:decision id="decision" decider="decider">
        <batch:end on="NOTOK" />
        <batch:next on="OK" to="step2"/>
        <batch:fail on="*"/>
    </batch:decision>
    <batch:step id="step2" allow-start-if-complete="true">
        <batch:tasklet allow-start-if-complete="true">
            <batch:chunk reader="reader" writer="writer" commit-interval="1000" />
        </batch:tasklet>
    </batch:step>
</batch:job>

Can anyone give me a hint how I to get my java configuration working, i.e. step beans are instanciated during job execution?

2
I saw the same exception today and it totally makes sense: Inside of simpleJob() you call step2() which requires a job context, but there is none at init time! This would have been my answer, but what puzzles me is that you write, you had this working with xml...realsim
Yes and the fact, that using a java-based non-conditional flow is also working fine; in that case the job builder is able to build the job - even the steps are not intitialized due to step scope... I haven't debug the JobBuilderFactory yet to understand the difference between non-conditional and conditional.Rainer Montag

2 Answers

1
votes

It could be that your are running into BATCH-2229: Unable to use Job Scope beans in a multi-threaded or partitioned step which is currently unresolved.

This was raised in response to accessing Job Scoped beans from partitioned and/or multi-threaded steps:

Access @JobScope bean in spring batch with partitioned step

Multi-threaded acces to Job Scope beans in Spring Batch 3.0

0
votes

I think the problem is "step1(null)", try to change like this:

public Job simpleJob(Step step1) {
Job job = jobBuilderFactory().get(JOB_NAME)
        .start(step1)
        .next(decider())
        .on("OK")
        .to(step2())
        .end()
        .build();
return job;}