控制 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通常也是回答“如何建立作業之間的依賴關係?”這個問題的很好答案。這是將大型系統分解為更小的模組並控制作業流程的好方法。