控制 Step 流程
通过将步骤分组到一个所有者作业中,需要能够控制作业如何从一个步骤“流”向另一个步骤。一个Step的失败不一定意味着Job应该失败。此外,可能存在不止一种“成功”类型,决定接下来应该执行哪个Step。根据一组Steps的配置方式,某些步骤甚至可能根本不被处理。
|
流定义中的步骤 Bean 方法代理
在一个流定义中,步骤实例必须是唯一的。当一个步骤在一个流定义中有多个结果时,将相同的步骤实例传递给流定义方法( 在以下示例中,步骤作为参数注入到流或作业 Bean 定义方法中。这种依赖注入方式保证了流定义中步骤的唯一性。然而,如果流是通过调用带有 有关 Spring Framework 中 Bean 方法代理的更多详细信息,请参阅使用 @Configuration 注解部分。 |
顺序流程
最简单的流程场景是所有步骤按顺序执行的作业,如下图所示
这可以通过在step中使用next来实现。
-
Java
-
XML
以下示例展示了如何在 Java 中使用next()方法
@Bean
public Job job(JobRepository jobRepository, Step stepA, Step stepB, Step stepC) {
return new JobBuilder("job", jobRepository)
.start(stepA)
.next(stepB)
.next(stepC)
.build();
}
以下示例展示了如何在 XML 中使用next属性
<job id="job">
<step id="stepA" parent="s1" next="stepB" />
<step id="stepB" parent="s2" next="stepC"/>
<step id="stepC" parent="s3" />
</job>
在上述场景中,stepA首先运行,因为它是列出的第一个Step。如果stepA正常完成,stepB运行,依此类推。但是,如果step A失败,整个Job失败,并且stepB不执行。
使用 Spring Batch XML 命名空间时,配置中列出的第一个步骤总是由Job运行的第一个步骤。其他步骤元素的顺序无关紧要,但第一个步骤必须始终出现在 XML 中的第一个位置。 |
条件流
在前面的例子中,只有两种可能性
-
step成功,下一个step应该被执行。 -
step失败,因此,job应该失败。
在许多情况下,这可能就足够了。然而,如果一个step的失败应该触发不同的step,而不是导致失败,那该怎么办呢?下图显示了这样的流程
-
Java
-
XML
Java API 提供了一组流畅的方法,让您可以指定流程以及在步骤失败时该怎么做。以下示例展示了如何指定一个步骤 (stepA),然后根据stepA是否成功,继续执行两个不同步骤 (stepB或stepC) 中的一个
@Bean
public Job job(JobRepository jobRepository, Step stepA, Step stepB, Step stepC) {
return new JobBuilder("job", jobRepository)
.start(stepA)
.on("*").to(stepB)
.from(stepA).on("FAILED").to(stepC)
.end()
.build();
}
为了处理更复杂的场景,Spring Batch XML 命名空间允许您在 step 元素内定义 transition 元素。其中一个 transition 是next元素。与next属性一样,next元素告诉Job接下来要执行哪个Step。然而,与属性不同的是,一个给定的Step上允许任意数量的next元素,并且在失败情况下没有默认行为。这意味着,如果使用 transition 元素,则必须明确定义Step转换的所有行为。还要注意,单个步骤不能同时具有next属性和transition元素。
next元素指定要匹配的模式和接下来要执行的步骤,如下例所示
<job id="job">
<step id="stepA" parent="s1">
<next on="*" to="stepB" />
<next on="FAILED" to="stepC" />
</step>
<step id="stepB" parent="s2" next="stepC" />
<step id="stepC" parent="s3" />
</job>
-
Java
-
XML
使用 Java 配置时,on()方法使用简单的模式匹配方案来匹配Step执行结果的ExitStatus。
使用 XML 配置时,过渡元素的on属性使用简单的模式匹配方案来匹配Step执行结果的ExitStatus。
模式中只允许两个特殊字符
-
*匹配零个或多个字符 -
?精确匹配一个字符
例如,c*t匹配cat和count,而c?t匹配cat但不匹配count。
虽然Step上的转换元素数量没有限制,但如果Step执行结果的ExitStatus未被任何元素覆盖,框架将抛出异常,并且Job将失败。框架会自动将转换从最具体到最不具体的顺序排列。这意味着,即使stepA在上述示例中的顺序被交换,FAILED的ExitStatus仍会转到stepC。
批处理状态与退出状态
在为条件流配置Job时,理解BatchStatus和ExitStatus之间的区别很重要。BatchStatus是一个枚举,它是JobExecution和StepExecution的属性,由框架用于记录Job或Step的状态。它可以是以下值之一:COMPLETED、STARTING、STARTED、STOPPING、STOPPED、FAILED、ABANDONED或UNKNOWN。它们大多不言自明:当步骤或作业成功完成时设置COMPLETED状态,当它失败时设置FAILED状态,依此类推。
-
Java
-
XML
以下示例包含使用 Java 配置时的on元素
...
.from(stepA).on("FAILED").to(stepB)
...
以下示例包含使用 XML 配置时的next元素
<next on="FAILED" to="stepB" />
乍一看,on似乎引用了它所属Step的BatchStatus。然而,它实际上引用了Step的ExitStatus。顾名思义,ExitStatus表示Step执行完成后的状态。
-
Java
-
XML
使用 Java 配置时,上述 Java 配置示例中显示的on()方法引用了ExitStatus的退出代码。
更具体地说,当使用 XML 配置时,前面 XML 配置示例中显示的next元素引用了ExitStatus的退出代码。
用英语来说,它的意思是:“如果退出代码是 FAILED,则转到 stepB”。默认情况下,退出代码总是与Step的BatchStatus相同,这就是前面条目有效的原因。但是,如果退出代码需要不同怎么办?一个很好的例子来自示例项目中的跳过示例作业
-
Java
-
XML
以下示例展示了如何在 Java 中使用不同的退出代码
@Bean
public Job job(JobRepository jobRepository, Step step1, Step step2, Step errorPrint1) {
return new JobBuilder("job", jobRepository)
.start(step1).on("FAILED").end()
.from(step1).on("COMPLETED WITH SKIPS").to(errorPrint1)
.from(step1).on("*").to(step2)
.end()
.build();
}
以下示例展示了如何在 XML 中使用不同的退出代码
<step id="step1" parent="s1">
<end on="FAILED" />
<next on="COMPLETED WITH SKIPS" to="errorPrint1" />
<next on="*" to="step2" />
</step>
step1有三种可能性
-
Step失败,在这种情况下,作业应该失败。 -
Step成功完成。 -
Step成功完成,但退出代码为COMPLETED WITH SKIPS。在这种情况下,应运行不同的步骤来处理错误。
上述配置有效。但是,需要根据执行中跳过记录的条件来更改退出代码,如下例所示
public class SkipCheckingListener implements StepExecutionListener {
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
String exitCode = stepExecution.getExitStatus().getExitCode();
if (!exitCode.equals(ExitStatus.FAILED.getExitCode()) &&
stepExecution.getSkipCount() > 0) {
return new ExitStatus("COMPLETED WITH SKIPS");
} else {
return null;
}
}
}
前面的代码是一个StepExecutionListener,它首先检查以确保Step成功,然后检查StepExecution上的跳过计数是否大于 0。如果两个条件都满足,则返回一个带有退出代码COMPLETED WITH SKIPS的新ExitStatus。
配置停止
在讨论了BatchStatus和ExitStatus之后,人们可能会想,Job的BatchStatus和ExitStatus是如何确定的。虽然这些状态是由执行的代码为Step确定的,但Job的状态是根据配置确定的。
到目前为止,所有讨论的作业配置都至少有一个没有过渡的最终Step。
-
Java
-
XML
在下面的 Java 示例中,在step执行后,Job结束
@Bean
public Job job(JobRepository jobRepository, Step step1) {
return new JobBuilder("job", jobRepository)
.start(step1)
.build();
}
在下面的 XML 示例中,在step执行后,Job结束
<step id="step1" parent="s3"/>
如果未为Step定义任何转换,则Job的状态定义如下
-
如果
Step以FAILED的ExitStatus结束,则Job的BatchStatus和ExitStatus都为FAILED。 -
否则,
Job的BatchStatus和ExitStatus都为COMPLETED。
虽然这种终止批处理作业的方法对于某些批处理作业(例如简单的顺序步骤作业)是足够的,但可能需要自定义定义的作业停止场景。为此,Spring Batch 提供了三个过渡元素来停止Job(除了我们之前讨论的next元素)。这些停止元素中的每一个都以特定的BatchStatus停止Job。重要的是要注意,停止过渡元素对Job中任何Steps的BatchStatus或ExitStatus都没有影响。这些元素仅影响Job的最终状态。例如,作业中的每个步骤都可能具有FAILED状态,但作业可能具有COMPLETED状态。
在步骤处结束
配置步骤结束指示Job以COMPLETED的BatchStatus停止。已完成状态为COMPLETED的Job无法重新启动(框架会抛出JobInstanceAlreadyCompleteException)。
-
Java
-
XML
使用 Java 配置时,end方法用于此任务。end方法还允许一个可选的exitStatus参数,您可以使用它来自定义Job的ExitStatus。如果未提供exitStatus值,则ExitStatus默认情况下为COMPLETED,以匹配BatchStatus。
使用 XML 配置时,可以使用end元素来完成此任务。end元素还允许一个可选的exit-code属性,您可以使用它来自定义Job的ExitStatus。如果未给出exit-code属性,则ExitStatus默认情况下为COMPLETED,以匹配BatchStatus。
考虑以下场景:如果step2失败,Job以COMPLETED的BatchStatus和COMPLETED的ExitStatus停止,并且step3不运行。否则,执行移动到step3。请注意,如果step2失败,Job不可重启(因为状态为COMPLETED)。
-
Java
-
XML
以下示例展示了 Java 中的场景
@Bean
public Job job(JobRepository jobRepository, Step step1, Step step2, Step step3) {
return new JobBuilder("job", jobRepository)
.start(step1)
.next(step2)
.on("FAILED").end()
.from(step2).on("*").to(step3)
.end()
.build();
}
以下示例展示了 XML 中的场景
<step id="step1" parent="s1" next="step2">
<step id="step2" parent="s2">
<end on="FAILED"/>
<next on="*" to="step3"/>
</step>
<step id="step3" parent="s3">
使步骤失败
配置步骤在给定点失败,指示Job以FAILED的BatchStatus停止。与结束不同,Job的失败不会阻止Job重新启动。
使用 XML 配置时,fail元素还允许一个可选的exit-code属性,该属性可用于自定义Job的ExitStatus。如果没有给定exit-code属性,ExitStatus默认情况下为FAILED,以匹配BatchStatus。
考虑以下场景:如果step2失败,Job将以FAILED的BatchStatus和EARLY TERMINATION的ExitStatus停止,并且step3不执行。否则,执行将移动到step3。此外,如果step2失败并且Job重新启动,执行将再次从step2开始。
-
Java
-
XML
以下示例展示了 Java 中的场景
@Bean
public Job job(JobRepository jobRepository, Step step1, Step step2, Step step3) {
return new JobBuilder("job", jobRepository)
.start(step1)
.next(step2).on("FAILED").fail()
.from(step2).on("*").to(step3)
.end()
.build();
}
以下示例展示了 XML 中的场景
<step id="step1" parent="s1" next="step2">
<step id="step2" parent="s2">
<fail on="FAILED" exit-code="EARLY TERMINATION"/>
<next on="*" to="step3"/>
</step>
<step id="step3" parent="s3">
在给定步骤处停止作业
配置作业在特定步骤停止,指示Job以STOPPED的BatchStatus停止。停止Job可以提供处理的临时中断,以便操作员在重新启动Job之前可以采取一些措施。
-
Java
-
XML
使用 Java 配置时,stopAndRestart方法需要一个restart属性,该属性指定在重新启动作业时执行应从哪个步骤开始。
使用 XML 配置时,stop元素需要一个restart属性,该属性指定在Job重新启动时执行应从哪个步骤开始。
考虑以下场景:如果step1以COMPLETE结束,则作业停止。一旦重新启动,执行将从step2开始。
-
Java
-
XML
以下示例展示了 Java 中的场景
@Bean
public Job job(JobRepository jobRepository, Step step1, Step step2) {
return new JobBuilder("job", jobRepository)
.start(step1).on("COMPLETED").stopAndRestart(step2)
.end()
.build();
}
以下列表显示了 XML 中的场景
<step id="step1" parent="s1">
<stop on="COMPLETED" restart="step2"/>
</step>
<step id="step2" parent="s2"/>
程序化流程决策
在某些情况下,可能需要比ExitStatus更多的信息来决定接下来执行哪个步骤。在这种情况下,可以使用JobExecutionDecider来辅助决策,如下例所示
public class MyDecider implements JobExecutionDecider {
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
String status;
if (someCondition()) {
status = "FAILED";
}
else {
status = "COMPLETED";
}
return new FlowExecutionStatus(status);
}
}
-
Java
-
XML
在下面的示例中,当使用 Java 配置时,实现了JobExecutionDecider的 Bean 直接传递给next调用
@Bean
public Job job(JobRepository jobRepository, MyDecider decider, Step step1, Step step2, Step step3) {
return new JobBuilder("job", jobRepository)
.start(step1)
.next(decider).on("FAILED").to(step2)
.from(decider).on("COMPLETED").to(step3)
.end()
.build();
}
在以下示例作业配置中,decision指定了要使用的决策器以及所有转换
<job id="job">
<step id="step1" parent="s1" next="decision" />
<decision id="decision" decider="decider">
<next on="FAILED" to="step2" />
<next on="COMPLETED" to="step3" />
</decision>
<step id="step2" parent="s2" next="step3"/>
<step id="step3" parent="s3" />
</job>
<beans:bean id="decider" class="com.MyDecider"/>
拆分流程
到目前为止所描述的每个场景都涉及一个作业,该作业以线性方式一次执行一个步骤。除了这种典型样式之外,Spring Batch 还允许配置具有并行流的作业。
-
Java
-
XML
基于 Java 的配置允许您通过提供的构建器配置拆分。如下例所示,split元素包含一个或多个flow元素,可以在其中定义完全独立的流。split元素还可以包含任何前面讨论的转换元素,例如next属性或next、end或fail元素。
@Bean
public Flow flow1(Step step1, Step step2) {
return new FlowBuilder<SimpleFlow>("flow1")
.start(step1)
.next(step2)
.build();
}
@Bean
public Flow flow2(Step step3) {
return new FlowBuilder<SimpleFlow>("flow2")
.start(step3)
.build();
}
@Bean
public Job job(JobRepository jobRepository, Flow flow1, Flow flow2, Step step4) {
return new JobBuilder("job", jobRepository)
.start(flow1)
.split(new SimpleAsyncTaskExecutor())
.add(flow2)
.next(step4)
.end()
.build();
}
XML 命名空间允许您使用split元素。如下例所示,split元素包含一个或多个flow元素,其中可以定义完全独立的流。split元素还可以包含任何前面讨论的转换元素,例如next属性或next、end或fail元素。
<split id="split1" next="step4">
<flow>
<step id="step1" parent="s1" next="step2"/>
<step id="step2" parent="s2"/>
</flow>
<flow>
<step id="step3" parent="s3"/>
</flow>
</split>
<step id="step4" parent="s4"/>
外部化流程定义和作业之间的依赖关系
作业中的一部分流程可以作为单独的 Bean 定义外部化,然后重用。有两种方法可以实现。第一种是将流程声明为对其他地方定义的流程的引用。
-
Java
-
XML
以下 Java 示例展示了如何将流声明为对其他地方定义的流的引用
@Bean
public Job job(JobRepository jobRepository, Flow flow1, Step step3) {
return new JobBuilder("job", jobRepository)
.start(flow1)
.next(step3)
.end()
.build();
}
@Bean
public Flow flow1(Step step1, Step step2) {
return new FlowBuilder<SimpleFlow>("flow1")
.start(step1)
.next(step2)
.build();
}
以下 XML 示例展示了如何将流声明为对其他地方定义的流的引用
<job id="job">
<flow id="job1.flow1" parent="flow1" next="step3"/>
<step id="step3" parent="s3"/>
</job>
<flow id="flow1">
<step id="step1" parent="s1" next="step2"/>
<step id="step2" parent="s2"/>
</flow>
如上例所示,定义外部流的效果是:将外部流中的步骤插入到作业中,就像它们是内联声明的一样。通过这种方式,许多作业可以引用相同的模板流,并将这些模板组合成不同的逻辑流。这也是分离各个流的集成测试的好方法。
外部化流的另一种形式是使用JobStep。JobStep类似于FlowStep,但它实际上为指定流中的步骤创建并启动一个单独的作业执行。
-
Java
-
XML
以下示例展示了 Java 中JobStep的示例
@Bean
public Job jobStepJob(JobRepository jobRepository, Step jobStepJobStep1) {
return new JobBuilder("jobStepJob", jobRepository)
.start(jobStepJobStep1)
.build();
}
@Bean
public Step jobStepJobStep1(JobRepository jobRepository, JobLauncher jobLauncher, Job job, JobParametersExtractor jobParametersExtractor) {
return new StepBuilder("jobStepJobStep1", jobRepository)
.job(job)
.launcher(jobLauncher)
.parametersExtractor(jobParametersExtractor)
.build();
}
@Bean
public Job job(JobRepository jobRepository) {
return new JobBuilder("job", jobRepository)
// ...
.build();
}
@Bean
public DefaultJobParametersExtractor jobParametersExtractor() {
DefaultJobParametersExtractor extractor = new DefaultJobParametersExtractor();
extractor.setKeys(new String[]{"input.file"});
return extractor;
}
以下示例展示了 XML 中JobStep的示例
<job id="jobStepJob" restartable="true">
<step id="jobStepJob.step1">
<job ref="job" job-launcher="jobLauncher"
job-parameters-extractor="jobParametersExtractor"/>
</step>
</job>
<job id="job" restartable="true">...</job>
<bean id="jobParametersExtractor" class="org.spr...DefaultJobParametersExtractor">
<property name="keys" value="input.file"/>
</bean>
作业参数提取器是一种策略,它决定如何将Step的ExecutionContext转换为要运行的Job的JobParameters。当您希望对作业和步骤进行更细粒度的监控和报告选项时,JobStep非常有用。使用JobStep通常也是回答“如何创建作业之间的依赖关系?”这个问题的很好答案。这是将大型系统分解为更小的模块并控制作业流程的好方法。