控制步骤流程
能够将步骤组合到拥有作业中,也意味着需要能够控制作业如何从一个步骤“流向”另一个步骤。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 命名空间允许您在步骤元素内定义转换元素。其中一个转换是 next
元素。与 next
属性类似,next
元素告诉 Job
接下来执行哪个 Step
。但是,与属性不同,在给定的 Step
上允许使用任意数量的 next
元素,并且在失败的情况下没有默认行为。这意味着,如果使用转换元素,则必须明确定义 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 配置时,transition 元素的 on
属性使用简单的模式匹配方案来匹配 Step
执行结果产生的 ExitStatus
。
模式中只允许使用两个特殊字符。
-
*
匹配零个或多个字符。 -
?
匹配恰好一个字符。
例如,c*t
匹配 cat
和 count
,而 c?t
匹配 cat
但不匹配 count
。
虽然 Step
上的 transition 元素数量没有限制,但如果 Step
执行产生的 ExitStatus
未被任何元素覆盖,框架将抛出异常,并且 Job
失败。框架会自动将转换从最具体到最不具体进行排序。这意味着,即使 stepA
的顺序被交换,ExitStatus
为 FAILED
仍然会进入 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。如果这两个条件都满足,则返回一个新的 ExitStatus
,其退出代码为 COMPLETED WITH SKIPS
。
配置停止
在讨论了 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
以ExitStatus
为FAILED
结束,则Job
的BatchStatus
和ExitStatus
均为FAILED
。 -
否则,
Job
的BatchStatus
和ExitStatus
均为COMPLETED
。
虽然这种终止批处理作业的方法对于某些批处理作业(例如简单的顺序步骤作业)来说已经足够了,但可能需要自定义定义的作业停止方案。为此,Spring Batch 提供了三个转换元素来停止 Job
(除了我们之前讨论过的 next
元素)。每个停止元素都以特定的 BatchStatus
停止 Job
。需要注意的是,停止转换元素对 Job
中任何 Steps
的 BatchStatus
或 ExitStatus
均无影响。这些元素仅影响 Job
的最终状态。例如,作业中的每个步骤都可能具有 FAILED
状态,但作业的状态可能为 COMPLETED
。
在步骤处结束
配置步骤结束指示 Job
以 BatchStatus
为 COMPLETED
停止。以 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
以 BatchStatus
为 COMPLETED
和 ExitStatus
为 COMPLETED
停止,并且 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
以 BatchStatus
为 FAILED
停止。与 end
不同,Job
的失败不会阻止 Job
重新启动。
当使用 XML 配置时,fail
元素还允许使用可选的 exit-code
属性来自定义 Job
的 ExitStatus
。如果未给出 exit-code
属性,则 ExitStatus
默认情况下为 FAILED
,以匹配 BatchStatus
。
考虑以下场景:如果 step2
失败,则 Job
以 BatchStatus
为 FAILED
和 ExitStatus
为 EARLY TERMINATION
停止,并且 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
以 BatchStatus
为 STOPPED
停止。停止 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
在以下示例中,实现 JobExecutionDecider
的 bean 在使用 Java 配置时直接传递给 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"/>
分支流程
到目前为止描述的每个场景都涉及一个 Job
,它一次按线性方式依次执行其步骤。除了这种典型风格之外,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
通常也是解决“如何创建作业之间的依赖关系?”这个问题的一个好方法。它是一种将大型系统分解成较小模块并控制作业流程的好方法。