Repository: spring-projects/spring-batch

Issue #857: Conditional (Java configured) steps cannot be scoped [BATCH-2747]
Created: 2018-08-26T13:13:50Z
Mattias Jiderhamn opened BATCH-2747 and commented
Configuring conditional steps using Java config (XML untested) fails if any of the steps is scoped using Job scope or Step scope.
To reproduce, use the example at https://docs.spring.io/spring-batch/4.0.x/reference/html/step.html#controllingStepFlow, i.e.
@Bean
public Job job() {
        return this.jobBuilderFactory.get("job")
                                .start(stepA())
                                .on("*").to(stepB())
                                .from(stepA()).on("FAILED").to(stepC())
                                .end()
                                .build();
}
and then add @JobScope or @StepScope on stepA(), stepB() or stepC().
The underlying reason is that to configure a flow step, the framework wants to get the steps name using getName(), at which point the bean is resolved and the scope inferred.
Stack trace (4.0.1)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myJob' defined in se.jiderhamn.ScopeTestConfiguration: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.core.Job]: Factory method 'parseCallLogJob' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.stepA': 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:583)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1249)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1098)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:502)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:312)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:310)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:756)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:868)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:128)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:109)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:246)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117)
	... 24 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.core.Job]: Factory method 'parseCallLogJob' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.stepA': 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:186)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:575)
	... 41 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.stepA': 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:357)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
	at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:193)
	at com.sun.proxy.$Proxy32.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.on(SimpleJobBuilder.java:91)
	at se.jiderhamn.ScopeTestConfiguration.parseCallLogJob(ScopeTestConfiguration.java:36)
	at se.jiderhamn.ScopeTestConfiguration$$EnhancerBySpringCGLIB$$240ba886.CGLIB$parseCallLogJob$3(<generated>)
	at se.jiderhamn.ScopeTestConfiguration$$EnhancerBySpringCGLIB$$240ba886$$FastClassBySpringCGLIB$$faf3ccf4.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:361)
	at se.jiderhamn.ScopeTestConfiguration$$EnhancerBySpringCGLIB$$240ba886.parseCallLogJob(<generated>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:155)
	... 42 more
Caused by: java.lang.IllegalStateException: No context holder available for job scope
	at org.springframework.batch.core.scope.JobScope.getContext(JobScope.java:159)
	at org.springframework.batch.core.scope.JobScope.get(JobScope.java:92)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:345)
	... 62 more


Affects: 4.0.1, 4.1.0.M2
Reference URL: https://stackoverflow.com/questions/55571210/
2 votes, 4 watchers

Duplicate issue #3640: Error creating bean with name 'scopedTarget.initialStep': Scope 'job' is not active for the current thread
Created: 2020-01-01T12:24:42Z
When I try to configure a job using the Conditional Flow API such as the following:
   @Bean(FILE_JOB)
    public Job generalFileJob(@Qualifier(INITIAL_STEP) Step initialStep, 
            @Qualifier(DOWNLOAD_STEP) Step downloadStep,
            @Qualifier(VALIDATION_STEP) Step validationStep, 
            @Qualifier(SEND_MSG_STEP) Step sendMsgStep,
            CommonJobListener jobListener) {

        return this.jobBuilderFactory.get(FILE_JOB)
                .listener(jobListener)
                .start(initialStep).on(COMPLETE_WITH_EMPTY).end()
                .from(initialStep).on(COMPLETE).to(downloadStep)
                .on(COMPLETE).to(validationStep)
                .on(COMPLETE).to(sendMsgStep)
                .end()
                .build();
    }

    @JobScope
    @Bean(INITIAL_STEP)
    public Step initialStep(QueryFraudFileTasklet queryFraudFileTasklet) {
        return this.stepBuilderFactory.get(INITIAL_STEP)
                .tasklet(queryFraudFileTasklet)
                .allowStartIfComplete(true)
                .listener(QueryFraudFileListener.INSTANCE)
                .build();
    }

There will be exception thrown from this process with the following exception stack:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.initialStep': 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:365)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:193)
	at com.sun.proxy.$Proxy107.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.on(SimpleJobBuilder.java:91)
	at com.mastercard.cnswitch.risk.batch.fraudfile.configuration.FraudFileJobConfiguration.fraudFileJob(FraudFileJobConfiguration.java:55)
	at com.mastercard.cnswitch.risk.batch.fraudfile.configuration.FraudFileJobConfiguration$$EnhancerBySpringCGLIB$$a113d60d.CGLIB$fraudFileJob$2(<generated>)
	at com.mastercard.cnswitch.risk.batch.fraudfile.configuration.FraudFileJobConfiguration$$EnhancerBySpringCGLIB$$a113d60d$$FastClassBySpringCGLIB$$4e33f171.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363)
	at com.mastercard.cnswitch.risk.batch.fraudfile.configuration.FraudFileJobConfiguration$$EnhancerBySpringCGLIB$$a113d60d.fraudFileJob(<generated>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
	... 20 common frames omitted
Caused by: java.lang.IllegalStateException: No context holder available for job scope
	at org.springframework.batch.core.scope.JobScope.getContext(JobScope.java:159)
	at org.springframework.batch.core.scope.JobScope.get(JobScope.java:92)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:353)
	... 40 common frames omitted

When to iterate the stack trace I find that the following method which tries to invoke bean's getName method and this will trigger the initialization of the real step bean. Because the step bean is in job scope but there's no job execution created so the exception is thrown.
private State createState(Object input) {
        State result;
        if (input instanceof Step) {
            if (!this.states.containsKey(input)) {
                Step step = (Step)input;
                this.states.put(input, new StepState(this.prefix + step.getName(), step));
            }

            result = (State)this.states.get(input);
        } else if (input instanceof JobExecutionDecider) {
            if (!this.states.containsKey(input)) {
                this.states.put(input, new DecisionState((JobExecutionDecider)input, this.prefix + "decision" + this.decisionCounter++));
            }

            result = (State)this.states.get(input);
        } else {
            if (!(input instanceof Flow)) {
                throw new FlowBuilderException("No state can be created for: " + input);
            }

            if (!this.states.containsKey(input)) {
                this.states.put(input, new FlowState((Flow)input, this.prefix + ((Flow)input).getName()));
            }

            result = (State)this.states.get(input);
        }

        this.dirty = true;
        return result;
    }

So I think this is the issue to process.

Closing comment (2020-02-28T22:27:54Z): This is a duplicate of #857.