3

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?

Rainer Montag
  • 493
  • 1
  • 6
  • 13
  • 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 Feb 09 '16 at 14:31
  • 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 Feb 09 '16 at 14:39

2 Answers2

1

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

Community
  • 1
  • 1
FGreg
  • 14,110
  • 10
  • 68
  • 110
  • I will check, if my configuration - basically the default Spring Batch Admin configuration - is affected by this issue. – Rainer Montag Feb 11 '16 at 09:15
0

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;}
Tao Li
  • 3
  • 1