简体   繁体   中英

Spring Batch - why is the job Step bean is being created/executed in the web context instead of the Job context?

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:

  1. Implemented the job registry to encapsulate the jobs, and updated the job launch to use the job registry - no change
  2. Both TARGET_CLASS and DEFAULT proxy mode have been tried and function the same - no change
  3. 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.

  1. 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.

  2. 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.

  3. 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.

You are using proxyMode = ScopedProxyMode.TARGET_CLASS on the scope of your reader, so you need to declare the step scope with:

<beans:bean class="org.springframework.batch.core.scope.StepScope">
   <beans:property name="proxyTargetClass" value="true" />
</beans:bean>

EDIT: I know that there is an open issue about beans that don't get proxied when mixing Java config and XML config (See BATCH-2351 ), but I'm not sure you are hitting that issue here.

Here are a couple of things I would try:

  • Don't use <context:component-scan base-package="com.xxxx.*" /> and declare the MyExampleReader using XML with scope="step" after removing @Component and @Scope(value = "step", proxyMode = ScopedProxyMode.TARGET_CLASS)
  • I don't understand the "Code that launches job" part and this looks unfamiliar compared to the typical way of launching jobs in a web application. If your Spring Batch application context is a child context of your web application context, then all beans defined in the batch context will be visible in your controller and you can inject a JobLauncher and the Job to launch. You can find an example here: https://docs.spring.io/spring-batch/4.1.x/reference/html/job.html#runningJobsFromWebContainer

A similar question can be found here: Spring batch scope issue while using spring boot

Hope this helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM