Inside my web application, I launch and manage several dozen spring-batch processes for long running operations.
It looks to me like spring-batch has built the job in the web application context instead of the job context, resulting in the non-informative error "No Scope registered for scope name 'step'" .
Any ideas what I am missing?
- Java version: 1.8
- Spring version: 5.1.3.RELEASE
- spring-batch version: 4.1.1.RELEASE
- Tomcat version: 8.0
Changes/updates made since the question was posted:
- Implemented the job registry to encapsulate the jobs, and updated the job launch to use the job registry - no change
- Both TARGET_CLASS and DEFAULT proxy mode have been tried and function the same - no change
- Added "<property name="proxyTargetClass" value="true" />" to the StepScope bean declaration per Mahmoud Ben Hassine's answer - no change
Log ...
taskExecutor-1 2019-02-12 13:31:32,836 ERROR o.s.b.c.s.AbstractStep - Encountered an error executing step step0002-init-prepareGraphDatastore in job hierarchy-analyser
java.lang.IllegalStateException: No Scope registered for scope name 'step'
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:350) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35) ~[spring-aop-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:672) ~[spring-aop-5.1.3.RELEASE.jar:5.1.3.RELEASE]
at com.xxxx.MyExampleReader$$EnhancerBySpringCGLIB$$b0c58048.beforeStep(<generated>) ~[relationship-analyzer-tool-BASELINE.jar:na]
at org.springframework.batch.core.listener.CompositeStepExecutionListener.beforeStep(CompositeStepExecutionListener.java:77) ~[spring-batch-core-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:199) ~[spring-batch-core-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:148) [spring-batch-core-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.batch.core.job.flow.JobFlowExecutor.executeStep(JobFlowExecutor.java:68) [spring-batch-core-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.batch.core.job.flow.support.state.StepState.handle(StepState.java:67) [spring-batch-core-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.batch.core.job.flow.support.SimpleFlow.resume(SimpleFlow.java:169) [spring-batch-core-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.batch.core.job.flow.support.SimpleFlow.start(SimpleFlow.java:144) [spring-batch-core-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.batch.core.job.flow.FlowJob.doExecute(FlowJob.java:136) [spring-batch-core-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:313) [spring-batch-core-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:144) [spring-batch-core-4.1.1.RELEASE.jar:4.1.1.RELEASE]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_162]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_162]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_162]
Spring-batch job XML ...
<?xml version="1.0" encoding="UTF-8"?>
<beans default-lazy-init="false"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:batch="http://www.springframework.org/schema/batch"
xsi:schemaLocation="
http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.1.xsd">
<description>Hierarchy Analyzer</description>
<context:component-scan
base-package="com.xxxx.*" />
<bean class="org.springframework.batch.core.scope.JobScope" />
<bean class="org.springframework.batch.core.scope.StepScope" />
<batch:job id="hierarchy-analyser">
<batch:listeners>
<batch:listener
ref="someJobListeners" />
</batch:listeners>
<batch:step id="step0002-init-long-running-process"
allow-start-if-complete="true">
<batch:tasklet
transaction-manager="jtaTransactionManager" start-limit="100">
<batch:chunk reader="myExampleReader"
writer="myExampleWriter" commit-interval="1" />
</batch:tasklet>
<batch:fail on="FAILED" />
<batch:next on="*"
to="step0002-1-more-stuff" />
<batch:listeners>
<batch:listener ref="myExampleReader" />
<batch:listener ref="myExampleWriter" />
</batch:listeners>
</batch:step>
</batch:job>
</beans>
MyExampleReader ...
@Component
@Scope(value = "step", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyExampleReader
implements ItemReader<TableMetadata>,
StepExecutionListener { ... }
Code that launches job ...
private ResultWrapper<Job> setupJobById(Resource r) throws Exception {
ResultWrapper<Job> result = new ResultWrapper<Job>();
try {
Resource[] res = new Resource[] { r };
ClasspathXmlApplicationContextsFactoryBean b = new ClasspathXmlApplicationContextsFactoryBean();
b.setApplicationContext(applicationContext);
b.setResources(res);
ApplicationContextJobFactory factory = new ApplicationContextJobFactory(
r.getFilename().substring(0, r.getFilename().lastIndexOf('.')), b.getObject()[0]);
result.succeed(factory.createJob());
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
result.fail(null, ex.getMessage(), ex);
}
return result;
}
Inside AbstractBeanFactory.doGetbean(), this is the contents of the spring context:
{request=org.springframework.web.context.request.RequestScope@3e707e1c, session=org.springframework.web.context.request.SessionScope@375463f}
Update: clarifications to answer
There were a number of code issues that contributed to this.
The class I was looking for was not in any of the context scan paths in the job, but it was in the global context scan path. Sanitizing code for public forums hid this from any responders.
The original code did not follow consistent practice for proxy modes throughout the application. There was no reason to not follow consistent best practices throughout.
Incorrect use of the job registry internals led to general "wierdness".
As to "why" in the original question, the answer is that Spring evaluates scope at context-scan time for each context. If the bean is not loaded with the job context (say because one of the classpaths required is missing from the job.xml file) then on lazy loading Spring attempts to load the bean, and finds one in the parent classpath, which happens to be the one scanned by the web config. The bean is declared "Step". The webconfig, of course has no step scope.
The error message is both correct (English: Yo dude, this bean is declared step scope, but there aint one in the context), and misleading (I can see in the job that there is a step scope, it's executing in the step scope, other beans are operating in the step scope, WTH ???!!!!!).
I would like to see more intelligent error messages returned from Spring. It is easy to lose days at a time chasing error messages that are completely accurate but hide the real source of the issue.