控制 Step 流程
透過將步驟分組到一個擁有 Job 中,需要能夠控制 Job 如何從一個步驟“流轉”到另一個步驟。Step
的失敗不一定意味著 Job
應該失敗。此外,可能有不止一種“成功”型別來決定接下來應該執行哪個 Step
。取決於如何配置一組 Steps
,某些步驟甚至可能完全不會被處理。
流程定義中的 Step Bean 方法代理
步驟例項在流程定義中必須是唯一的。當一個步驟在流程定義中有多個結果時,重要的是將同一個步驟例項傳遞給流程定義方法(如 在以下示例中,步驟作為引數注入到流程或 Job 的 bean 定義方法中。這種依賴注入方式保證了步驟在流程定義中的唯一性。然而,如果流程是透過呼叫使用 有關 Spring Framework 中 bean 方法代理的更多詳細資訊,請參閱使用 @Configuration 註解部分。 |
順序流程
最簡單的流程場景是 Job 中的所有步驟按順序執行,如下圖所示

這可以透過在 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 元素。其中一種轉換是 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 配置時,transition 元素的 on
屬性使用簡單的模式匹配方案來匹配 Step
執行產生的 ExitStatus
。
模式中只允許使用兩個特殊字元
-
*
匹配零個或多個字元 -
?
精確匹配一個字元
例如,c*t
匹配 cat
和 count
,而 c?t
匹配 cat
但不匹配 count
。
雖然 Step
上的 transition 元素數量沒有限制,但如果 Step
執行產生的 ExitStatus
未被任何元素覆蓋,框架會丟擲異常並導致 Job
失敗。框架會自動將 transition 元素按從最具體到最不具體的順序排列。這意味著,即使在前面示例中 stepA
的順序顛倒了,FAILED
的 ExitStatus
仍然會流轉到 stepC
。
BatchStatus 與 ExitStatus 的區別
為條件流程配置 Job
時,理解 BatchStatus
和 ExitStatus
之間的區別非常重要。BatchStatus
是 JobExecution
和 StepExecution
的一個屬性,是一個列舉型別,框架使用它來記錄 Job
或 Step
的狀態。它可以是以下值之一:COMPLETED
(完成),STARTING
(啟動中),STARTED
(已啟動),STOPPING
(停止中),STOPPED
(已停止),FAILED
(失敗),ABANDONED
(已放棄),或 UNKNOWN
(未知)。其中大多數都是自解釋的:COMPLETED
是步驟或 Job 成功完成時設定的狀態,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”。預設情況下,步驟的退出碼總是與其 BatchStatus
相同,這就是前面的配置起作用的原因。但是,如果退出碼需要不同怎麼辦?一個很好的例子來自 samples 專案中的 skip 示例 Job
-
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
失敗,在這種情況下 Job 應該失敗。 -
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
是如何確定的。雖然步驟的狀態是由執行的程式碼決定的,但 Job 的狀態是根據配置確定的。
到目前為止,所有討論的 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
。
雖然這種終止批處理 Job 的方法對於某些批處理 Job(例如簡單的順序步驟 Job)來說已經足夠,但可能需要自定義的 Job 停止場景。為此,Spring Batch 提供了三個 transition 元素來停止 Job
(除了我們之前討論的next
元素之外)。這些停止元素中的每一個都以特定的 BatchStatus
停止 Job
。重要的是要注意,停止 transition 元素對 Job
中任何 Steps
的 BatchStatus
或 ExitStatus
都沒有影響。這些元素僅影響 Job
的最終狀態。例如,Job 中的每個步驟都可能具有 FAILED
狀態,但 Job 卻具有 COMPLETED
狀態。
在 Step 處結束
配置步驟結束會指示 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">
使 Step 失敗
配置一個步驟在給定點失敗會指示 Job
以 FAILED
的 BatchStatus
停止。與 end 不同的是,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">
在給定 Step 停止 Job
配置 Job 在特定步驟停止會指示 Job
以 STOPPED
的 BatchStatus
停止。停止 Job
可以為處理提供一個臨時中斷,以便操作員可以在重啟 Job
之前採取一些行動。
-
Java
-
XML
使用 Java 配置時,stopAndRestart
方法需要一個 restart
屬性,該屬性指定 Job 重啟時應從哪個步驟繼續執行。
使用 XML 配置時,stop
元素需要一個 restart
屬性,該屬性指定 Job
重啟時應從哪個步驟繼續執行。
考慮以下場景:如果 step1
以 COMPLETE
結束,則 Job 停止。一旦重啟,執行將從 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();
}
在以下示例 Job 配置中,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 還允許 Job 配置並行流程。
-
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"/>
外部化流程定義和 Job 之間的依賴關係
Job 中的一部分流程可以作為單獨的 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>
如前例所示,定義外部流程的效果是將外部流程中的步驟插入到 Job 中,就像它們是內聯宣告的一樣。透過這種方式,許多 Job 可以引用相同的模板流程,並將這些模板組合成不同的邏輯流程。這也是分離各個流程整合測試的好方法。
外部化流程的另一種形式是使用 JobStep
。JobStep
類似於 FlowStep
,但實際上會為指定流程中的步驟建立並啟動一個單獨的 Job 執行。
-
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>
Job 引數提取器是一種策略,它決定了如何將 Step
的 ExecutionContext
轉換為要執行的 Job
的 JobParameters
。JobStep
在你想要對 Job 和步驟進行更細粒度的監控和報告時非常有用。使用 JobStep
也常常是回答“如何建立 Job 之間的依賴關係?”這個問題的好方法。它是將大型系統分解為更小模組並控制 Job 流程的好方法。