【问题标题】:How can I define multiple jobs dynamically in Spring Batch?如何在 Spring Batch 中动态定义多个作业?
【发布时间】:2018-05-18 01:10:40
【问题描述】:

我有一个使用 Spring Batch 定义预设数量的作业的应用程序,这些作业目前都在 XML 中定义。

随着时间的推移,我们会添加更多作业,这需要更新 XML,但是这些作业始终基于同一个父级,并且可以使用简单的 SQL 查询轻松地预先确定。

所以我一直在尝试切换到使用 XML 配置和基于 Java 的配置的某种组合,但很快就会感到困惑。

尽管我们有很多工作,但每个工作定义基本上都属于两个类别之一。所有作业都继承自一个或另一个父作业,并且实际上是相同的,只是名称不同。作业名称用于从数据库中选择不同的数据。

我想出了一些类似于以下的代码,但在使其正常工作时遇到了问题。

完全免责声明,我也不完全确定我会以正确的方式解决这个问题。稍后会详细介绍;一、代码:

@Configuration
@EnableBatchProcessing
public class DynamicJobConfigurer extends DefaultBatchConfigurer implements InitializingBean {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;
    @Autowired
    private JobRegistry jobRegistry;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private CustomJobDefinitionService customJobDefinitionService;

    private Flow injectedFlow1;
    private Flow injectedFlow2;

    public void setupJobs() throws DuplicateJobException {

        List<JobDefinition> jobDefinitions = customJobDefinitionService.getAllJobDefinitions();

        for (JobDefinition jobDefinition : jobDefinitions) {

            Job job = null;
            if (jobDefinition.getType() == 1) {
                job = jobBuilderFactory.get(jobDefinition.getName())
                        .start(injectedFlow1).build()
                        .build();
            } else if (jobDefinition.getType() == 2) {
                job = jobBuilderFactory.get(jobDefinition.getName())
                        .start(injectedFlow2).build()
                        .build();
            }

            if (job != null) {
                jobRegistry.register(new ReferenceJobFactory(job));
            }
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        setupJobs();
    }

    public void setInjectedFlow1(Flow injectedFlow1) {
        this.injectedFlow1 = injectedFlow1;
    }

    public void setInjectedFlow2(Flow injectedFlow2) {
        this.injectedFlow2 = injectedFlow2;
    }
}

我在 XML 中定义了注入的流,就像这样:

<batch:flow id="injectedFlow1">

    <batch:step id="InjectedFlow1.Step1" next="InjectedFlow1.Step2">
        <batch:flow parent="InjectedFlow.Step1" />
    </batch:step>

    <batch:step id="InjectedFlow1.Step2">
        <batch:flow parent="InjectedFlow.Step2" />
    </batch:step>

</batch:flow>

如您所见,我从InitializingBeanafterPropertiesSet() 方法有效地启动了setupJobs() 方法(旨在动态创建这些作业定义)。我不确定这是否正确。它正在运行,但我不确定是否有更适合此目的的不同入口点。我也不确定@Configuration 注释的意义是什么。

我目前遇到的问题是,当我从JobRegistry 调用register() 时,它会抛出以下IllegalStateException

要使用默认的 BatchConfigurer,上下文必须包含不超过一个 DataSource,找到 2。

注意:我的项目实际上定义了两个数据源。第一个是默认的 dataSource bean,它连接到 Spring Batch 使用的数据库。第二个数据源是一个外部数据库,第二个数据源包含定义我的作业列表所需的所有信息。但主要的确实使用默认名称“dataSource”,所以我不太确定我还能告诉它使用那个。

【问题讨论】:

  • 这些工作有什么不同?
  • @MichaelMinella 在工作定义级别 - 只有工作名称。一个单独的应用程序 POST 到 Spring Batch Admin 端点以根据名称启动作业,然后其中一个步骤使用作业名称从数据源中选择不同的东西。
  • 不过,请注意:在我的实际用例中,我们实际上定义了两个不同的流程(基于我在上面的伪代码中称为“类型”的内容)。这两个不同的流程确实有不同的步骤,但每项工作都是其中之一。
  • 如果您认为我可以只使用一两个工作,并将“名称”作为工作参数传递,那并不是我真正想要追求的选择。按名称分隔它们确实很重要,因为它允许我们通过 url.com/jobs/JobName 跟踪不同的执行,而不是将所有内容聚集在一起。尽管每份工作都基于一两个父母,但我们实际上总共有近 100 个。我们预计这个数字会随着时间的推移而继续增长。所以你可以明白为什么用 XML 定义它们最终是不可持续的。
  • 您是在 Spring Boot 中使用 Spring Batch 还是仅使用 Spring Batch?

标签: java xml spring spring-batch spring-batch-admin


【解决方案1】:

首先 - 我不建议同时使用 XML 和 Java 配置。只使用一个,最好是 Java 一个,因为将 XML 配置转换为 Java 配置并不费力。 (除非你有一些非常好的理由——你还没有解释过

我没有单独使用 Spring Batch,因为我一直将它与 Spring Boot 一起使用,并且我有一个项目,我在其中定义了多个作业,并且对于您展示的类似代码,它始终运行良好。

对于您的问题,有一些关于 SO 的答案,例如 thisthis,它们基本上是想说您需要编写自己的 BatchConfigurer 而不是依赖默认的 BatchConfigurer。

现在使用 Spring Boot 解决方案

使用 Spring Boot,您应该尝试分离作业定义和作业执行。 您应该首先尝试只定义作业并初始化 Spring 上下文而不启用作业 (spring.batch.job.enabled=false)

在您的 Spring Boot 主方法中,当您使用类似 - SpringApplication.run(Application.class, args); ...您将得到 ApplicationContext ctx 之类的东西启动应用程序时

现在您可以从此上下文中获取相关 bean 并通过从属性或命令行等获取名称并使用 JobLauncher.run(...) 方法启动特定作业。

如果您愿意订购作业执行,您可以参考my this answer。您还可以使用 Java 编写作业调度程序。

重点是,您将作业构建/bean 配置和作业执行问题分开。

挑战

当您尝试为每个作业设置不同的设置时,在单个项目中保留多个作业可能具有挑战性,因为 application.properties 文件是特定于环境而不是特定于作业的,即 Spring Boot 属性将适用于所有作业。

【讨论】:

    【解决方案2】:

    在我的特殊情况下,解决方案实际上是消除 @Configuration 和我上面班级的@EnableBatchProcessing 注释。这些问题导致它尝试使用 DefaultBatchConfigurer,当您定义了多个数据源时(即使您已经清楚地将它们识别为“dataSource”作为主要名称,而其他名称作为次要名称)。

    @Configuration 类并不是必需的,因为它真正所做的只是让您的类自动实例化,而无需在应用程序上下文中将其定义为 bean。不过反正我就是这么干的,这个就多余了。

    删除@EnableBatchProcessing 的缺点之一是我无法再自动连接 JobBuilderFactory bean。所以我只需要创建它:

        JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
        factory.setDataSource(dataSource);
        factory.setTransactionManager(transactionManager);
        factory.afterPropertiesSet();
        jobRepository = factory.getObject();
        jobBuilderFactory = new JobBuilderFactory(jobRepository);
    

    然后,使用jobRegistry.register(...) 定义我的工作,我似乎已经走上了正轨。所以基本上一旦我删除了上面的那些注释,一切都开始工作了。不过,我会将 Sabir 的答案标记为正确的答案,因为它帮助了我。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-08-29
      • 2014-12-10
      • 1970-01-01
      • 2016-03-16
      • 2016-11-14
      • 2014-02-09
      • 1970-01-01
      相关资源
      最近更新 更多