Controlling Step Flow
有了将步骤组合在一起,并将它们与拥有作业的能力,就需要能够控制作业如何从一个步骤“流向”另一个步骤。Step
的故障并不一定意味着 Job
应该失败。此外,可能有多种类型的“success
”决定应该执行哪一个 Step
。取决于 Steps
组的配置方式,某些步骤甚至可能根本不会被处理。
.Step bean method proxying in flow definitions
一个步骤实例在流程定义中必须是唯一的。当一个步骤在流程定义中具有多个结果时,重要的是将步骤的相同实例传递给流程定义方法(start
、from
等)。否则,流程执行可能会表现得非常意外。
在以下示例中,步骤被注入为流程或作业 bean 定义方法的参数。这种依赖项注入保证了流程定义中步骤的唯一性。但是,如果流程是通过调用带 @Bean
注释的步骤定义方法来定义的,那么如果禁用了 bean 方法代理(即 @Configuration(proxyBeanMethods = false)
),则步骤可能不是唯一的。如果首选 bean 间注入风格,则必须启用 bean 方法代理。
请参阅 Using the @Configuration annotation部分以获取有关在 Spring 框架中进行 Bean 方法代理的更多详细信息。
Sequential Flow
最简单的流程场景是其中所有步骤顺序执行的作业,如下所示:
这可以通过在 step
中使用 next
来实现。
- Java
-
以下示例展示了如何在 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
-
以下示例展示了如何在 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 命名空间中,在配置中列出的第一个步骤是_always_ 由 |
Conditional Flow
在前一个示例中,只存在两种可能性:
-
step
成功,应执行下一个step
。 -
step
失败,并且,因此,job
会失败。
在许多情况下,这可能就足够了。然而,对于一个场景又如何呢?在这种场景中,step
的故障应触发另一个 step
,而不是导致故障?下图展示了这样的一个流程:
- Java
-
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();
}
- XML
-
为了处理更复杂的场景,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 配置时,过渡元素的 on
属性使用一个简单的模式匹配方案来匹配 Step
执行过程中产生的 ExitStatus
。
模式中只允许两个特殊字符:
-
*
匹配零个或多个字符 -
?
匹配一个字符
例如,c*t
匹配 cat
和 count
,而 c?t
匹配 cat
但不匹配 count
。
虽然 Step
上的转换元素数量没有限制,但是,如果 Step
执行导致的 ExitStatus
并未被元素涵盖,那么框架将抛出异常,并且 Job
将失败。该框架会自动对转换按从最具体到最不具体进行排序。这意味着,即使在前面的示例中 stepA
的排序被交换,FAILED
的 ExitStatus
仍然会进入 stepC
。
Batch Status Versus Exit Status
为 Job
配置条件流程时,了解 BatchStatus
和 ExitStatus
之间的区别非常重要。BatchStatus
是一个枚举,既是 JobExecution
也是 StepExecution
的属性,并且由框架用于记录 Job
或 Step
的状态。它可以是以下值之一:COMPLETED
、STARTING
、STARTED
、STOPPING
、STOPPED
、FAILED
、ABANDONED
或 UNKNOWN
。其中多数是不言自明的:COMPLETED
是步骤或作业成功完成后设置的状态,FAILED
是失败后设置的状态,依此类推。
- Java
-
在使用 Java 配置时,以下示例包含
on
元素:
...
.from(stepA).on("FAILED").to(stepB)
...
- XML
-
在使用 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
-
以下示例显示了如何在 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
-
以下示例显示了如何在 XML 中使用不同的退出代码:
<step id="step1" parent="s1">
<end on="FAILED" />
<next on="COMPLETED WITH SKIPS" to="errorPrint1" />
<next on="*" to="step2" />
</step>
step1
有三种可能:
-
Step
失败,这时作业会失败。 -
The
Step
completed successfully. -
Step
成功完成,但退出码为COMPLETED WITH SKIPS
。在这种情况下,应运行其他步骤来处理错误。
前面的配置有效。但是,需要根据执行条件跳过记录来更改退出代码,如下面的示例所示:
public class SkipCheckingListener extends StepExecutionListenerSupport {
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
。
Configuring for Stop
在对 BatchStatus
and ExitStatus
进行讨论后,人们可能会好奇 `BatchStatus`和 `ExitStatus`如何针对 `Job`进行确定。虽然这些状态由执行的代码为 `Step`确定,但 `Job`的状态根据配置确定。
到目前为止,所讨论的所有作业配置都至少有一个无转换的最终 Step
。
- Java
-
在以下 Java 示例中,在
step
执行后,Job
结束:
@Bean
public Job job(JobRepository jobRepository, Step step1) {
return new JobBuilder("job", jobRepository)
.start(step1)
.build();
}
- XML
-
在以下 XML 示例中,在
step
执行后,Job
结束:
<step id="step1" parent="s3"/>
如果未为 Step
定义转换,则 Job
的状态将按如下方式定义:
-
如果
Step
以ExitStatus
或FAILED
结尾,那么Job
的BatchStatus
和ExitStatus
均为FAILED
。 -
否则,
Job
的BatchStatus
和ExitStatus
均为COMPLETED
。
虽然这种终止批处理作业的方法对于某些批处理作业(例如简单的顺序步骤作业)来说已经足够,但可能需要自定义定义的作业停止情形。为此,Spring Batch 提供三个转换元素来停止 Job
(除了我们之前讨论的 xref:step/controlling-flow.adoc#nextElement[next
元素)。这些停止元素中的每一个都以特定 BatchStatus
停止 Job
。需要注意的是,停止转换元素对 Job
中任何 Steps
的 BatchStatus
或 ExitStatus
都没有影响。这些元素仅影响 Job
的最终状态。例如,作业中的每个步骤的状态都可能是 FAILED
,但作业的状态为 COMPLETED
。
Ending at a Step
配置步骤结束会指导“作业”停止,其“批处理状态”为“完成”。已结束且状态为“完成”的“作业”无法重新启动(该框架会引发`JobInstanceAlreadyCompleteException`)。
-
Java
-
XML
使用 Java 配置时,end
方法用于该任务。end
方法还允许有一个可选的`exitStatus`参数,你可以用它来自定义“作业”的`ExitStatus`。如果未提供`exitStatus`值,那么为了与“批处理状态”匹配,ExitStatus`默认值为`COMPLETED(完成)
。
使用 XML 配置时,你可以使用`end`元素来执行该任务。end`元素还允许有一个可选的`exit-code`属性,你可以用它来自定义“作业”的`ExitStatus
。如果未提供`exit-code`属性,那么为了与“批处理状态”匹配,ExitStatus`默认值为`COMPLETED(已完成)
。
考虑以下场景:如果`step2`失败,那么“作业”会停止,“批处理状态”为`COMPLETED(已完成),`ExitStatus`为`COMPLETED(已完成)
,而且`step3`不会运行。否则,执行会转到`step3`。注意,如果`step2`失败,那么“作业”不可重新启动(因为状态为`COMPLETED(完成)`)。
- Java
-
以下示例用 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
-
以下示例用 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">
Failing a Step
配置步骤在某个点失败会指导“作业”停止,其“批处理状态”为`FAILED(失败)`。与`end`不同,“作业”发生故障并不会阻止“作业”重新启动。
使用 XML 配置时,fail`元素还允许有一个可选的`exit-code`属性,该属性可用于自定义“作业”的`ExitStatus
。如果未提供`exit-code`属性,那么为了与“批处理状态”匹配,ExitStatus`默认值为`FAILED(失败)
。
考虑以下场景:如果`step2`失败,那么“作业”会停止,“批处理状态”为`FAILED(失败),`ExitStatus`为`EARLY TERMINATION(早期终止)
,而且`step3`不会执行。否则,执行会转到`step3`。此外,如果`step2`失败,而且“作业”重新启动,那么会在`step2`重新开始执行。
- Java
-
以下示例用 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
-
以下示例用 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">
Stopping a Job at a Given Step
配置“作业”在特定步骤停止会指导“作业”停止,其“批处理状态”为`STOPPED(已停止)`。停止“作业”可以在处理过程中提供一个临时中断,以便操作者在重新启动“作业”前采取一些措施。
-
Java
-
XML
使用 Java 配置时,`stopAndRestart`方法需要一个`restart`属性,该属性指定“作业”重新启动后应该从哪个步骤开始执行。
使用 XML 配置时,`stop`元素需要一个`restart`属性,该属性指定“作业”重新启动后应该从哪个步骤开始执行。
考虑以下场景:如果`step1`完成的状态为`COMPLETE(已完成)`,那么“作业”随后会停止。在重新启动后,会从`step2`开始执行。
- Java
-
以下示例用 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
-
以下清单用 XML 展示了该场景:
<step id="step1" parent="s1">
<stop on="COMPLETED" restart="step2"/>
</step>
<step id="step2" parent="s2"/>
Programmatic Flow Decisions
在某些情况下,可能需要比`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
-
在以下示例中,使用 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();
}
- XML
-
在以下作业配置示例中,`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"/>
Split Flows
到目前为止描述的每一种情况都涉及一个“作业”,该“作业”以线性方式一次执行一个步骤。除了这种典型风格外,Spring Batch 还允许使用并行流来配置“作业”。
- Java
-
基于 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
-
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"/>
Externalizing Flow Definitions and Dependencies Between Jobs
作业中的部分流程可以被声明为一个单独的 bean 定义,然后被重用。有两种方法可以做到这一点。首先是声明流程为对其他地方定义的流程引用。
- Java
-
以下 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
-
以下 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
-
以下示例显示了 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
-
以下示例显示了 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
通常也是回答“如何在作业之间创建依赖关系?”这一问题的不错答案。它是一种将大型系统分解为更小的模块并控制作业流的好方法。