Spring Batch - 为啥在 Web 上下文而不是 Job 上下文中创建/执行作业 Step bean?

Posted

技术标签:

【中文标题】Spring Batch - 为啥在 Web 上下文而不是 Job 上下文中创建/执行作业 Step bean?【英文标题】:Spring Batch - why is the job Step bean is being created/executed in the web context instead of the Job context?Spring Batch - 为什么在 Web 上下文而不是 Job 上下文中创建/执行作业 Step bean? 【发布时间】:2019-07-06 13:16:24 【问题描述】:

在我的 Web 应用程序中,我启动并管理了几十个 spring-batch 进程以进行长时间运行的操作。

在我看来,spring-batch 已经在 Web 应用程序上下文而不是作业上下文中构建了作业,从而导致了非信息性错误“No Scope registered for scope name 'step'”。

任何想法我错过了什么?

Java 版本:1.8 春季版:5.1.3.RELEASE spring-batch 版本:4.1.1.RELEASE Tomcat 版本:8.0

自问题发布以来所做的更改/更新:

    实现了作业注册表以封装作业,并更新了作业启动以使用作业注册表 - 无变化 TARGET_CLASS 和 DEFAULT 代理模式都已尝试过并且功能相同 - 没有变化 根据 Mahmoud Ben Hassine 的回答在 StepScope bean 声明中添加了“” - 没有变化

日志...

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 作业 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  ... 

启动作业的代码...

    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;

在 AbstractBeanFactory.doGetbean() 中,这是 spring 上下文的内容:

request=org.springframework.web.context.request.RequestScope@3e707e1c, session=org.springframework.web.context.request.SessionScope@375463f

更新:澄清回答

有许多代码问题导致了这种情况。

    我要查找的类不在作业的任何上下文扫描路径中,但它在全局上下文扫描路径中。公共论坛的清理代码对任何响应者隐藏了这一点。

    原始代码在整个应用程序中没有遵循代理模式的一致做法。没有理由不始终遵循一致的最佳做法。

    作业注册表内部的错误使用导致了普遍的“怪异”。

至于原始问题中的“为什么”,答案是 Spring 在每个上下文的上下文扫描时评估范围。如果 bean 没有加载作业上下文(比如因为 job.xml 文件中缺少所需的类路径之一),那么在延迟加载时,Spring 会尝试加载 bean,并在父类路径中找到一个,这恰好是由网络配置扫描的那个。 bean 被声明为“Step”。 webconfig 当然没有 step 范围。

错误消息是正确的(英文:Yo dude,这个 bean 被声明为 step 范围,但在上下文中没有一个),并且具有误导性(我可以在作业中看到有一个 step 范围,它正在执行step 范围,其他 bean 都在 step 范围内运行,WTH ???!!!!!)。

我希望看到从 Spring 返回的更智能的错误消息。一次追踪完全准确但隐藏问题真正根源的错误消息很容易浪费数天时间。

【问题讨论】:

【参考方案1】:

您在阅读器范围内使用proxyMode = ScopedProxyMode.TARGET_CLASS,因此您需要声明步骤范围:

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

编辑:我知道在混合 Java 配置和 XML 配置时没有被代理的 bean 存在一个未解决的问题(请参阅 BATCH-2351),但我不确定您是否在这里遇到了这个问题。

以下是我会尝试的几件事:

不要使用&lt;context:component-scan base-package="com.xxxx.*" /&gt; 并在删除@Component@Scope(value = "step", proxyMode = ScopedProxyMode.TARGET_CLASS) 后使用带有scope="step" 的XML 声明MyExampleReader 我不理解“启动作业的代码”部分,与在 Web 应用程序中启动作业的典型方式相比,这看起来很陌生。如果您的 Spring Batch 应用程序上下文是您的 Web 应用程序上下文的子上下文,那么批处理上下文中定义的所有 bean 都将在您的控制器中可见,您可以注入 JobLauncherJob 来启动。你可以在这里找到一个例子:https://docs.spring.io/spring-batch/4.1.x/reference/html/job.html#runningJobsFromWebContainer

可以在这里找到类似的问题:Spring batch scope issue while using spring boot

希望这会有所帮助。

【讨论】:

谢谢,我去看看! 作业启动失败的地方和方式与此更改完全相同。在我看来,Sping 试图从错误的上下文中获取 bean——Web 应用程序而不是作业。当我追踪到抛出异常的地方时,工厂只显示了会话和请求上下文。 FTR,我的回答类似于***.com/a/43198232/5019386。您还可以通过将&lt;property name="autoProxy" value="true"/&gt; 添加到您的StepScope bean 定义来尝试自动代理bean。无论如何,我用我认为可能会解决您的问题的答案更新了答案,因为我无法从您分享的内容中看到未注册步骤范围的原因。 这让我找到了完整的答案,我将很快将其添加到问题文本中。

以上是关于Spring Batch - 为啥在 Web 上下文而不是 Job 上下文中创建/执行作业 Step bean?的主要内容,如果未能解决你的问题,请参考以下文章

应用上下文中一些bean的依赖在Spring Batch中形成了一个循环

SPRING-BATCH 错误:没有可用于步骤范围的上下文持有者

Spring Batch 中没有可用于作业范围的上下文持有者

Spring Batch JpaItemWriter vs HibernateItemWriter 以及为啥在使用 HibernateItemWriter 时需要 HibernateTransacti

为啥在尝试对整个 Spring Batch Job 进行单元测试时出现此错误?没有可用的“org.springframework.batch.core.Job”类型的合格bean

为啥 Spring Batch 在我的 Ubuntu 20.04 机器上的 MySql 上创建小写表?