控制步驟流程

藉由將步驟群組在一個擁有的 job 中,也產生了控制 job 如何從一個步驟「流動」到另一個步驟的需求。Step 的失敗不一定意味著 Job 應該失敗。此外,可能有多種類型的「成功」來決定接下來應該執行哪個 Step。根據一組 Steps 的配置方式,某些步驟甚至可能完全不會被處理。

流程定義中的步驟 bean 方法代理

在流程定義中,步驟實例必須是唯一的。當一個步驟在流程定義中有多個結果時,重要的是將同一個步驟實例傳遞給流程定義方法 (startfrom 等)。否則,流程執行可能會表現出非預期的行為。

在以下範例中,步驟以參數形式注入到流程或 job bean 定義方法中。這種依賴注入風格保證了流程定義中步驟的唯一性。然而,如果流程是透過呼叫以 @Bean 註解的步驟定義方法來定義的,那麼如果 bean 方法代理被禁用 (即 @Configuration(proxyBeanMethods = false)),步驟可能不是唯一的。如果偏好 bean 間注入風格,則必須啟用 bean 方法代理。

關於 Spring Framework 中 bean 方法代理的更多詳細資訊,請參考 使用 @Configuration 註解 章節。

循序流程

最簡單的流程情境是一個 job,其中所有步驟都依序執行,如下圖所示

Sequential Flow
圖 1. 循序流程

這可以透過在 step 中使用 next 來實現。

  • Java

  • XML

以下範例展示如何在 Java 中使用 next() 方法

Java 設定
@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 屬性

XML 設定
<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 中首先出現。

條件流程

在前面的範例中,只有兩種可能性

  1. step 成功,且應該執行下一個 step

  2. step 失敗,因此,job 應該失敗。

在許多情況下,這可能就足夠了。然而,如果 step 的失敗應該觸發不同的 step,而不是導致失敗,該怎麼辦? 下圖顯示了這樣一個流程

Conditional Flow
圖 2. 條件流程
  • Java

  • XML

Java API 提供了一組流暢的方法,讓您可以指定流程以及步驟失敗時該怎麼做。以下範例展示如何指定一個步驟 (stepA),然後根據 stepA 是否成功,繼續執行兩個不同的步驟 (stepBstepC) 之一

Java 設定
@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 元素中定義 transitions 元素。其中一個 transition 是 next 元素。與 next 屬性類似,next 元素告訴 Job 接下來要執行哪個 Step。然而,與屬性不同的是,允許在給定的 Step 上使用任意數量的 next 元素,並且在失敗的情況下沒有預設行為。這意味著,如果使用 transition 元素,則必須明確定義 Step transitions 的所有行為。另請注意,單個步驟不能同時具有 next 屬性和 transition 元素。

next 元素指定要匹配的模式和接下來要執行的步驟,如下例所示

XML 設定
<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 匹配 catcount,而 c?t 匹配 cat 但不匹配 count

雖然 Step 上的 transition 元素數量沒有限制,但如果 Step 執行產生的 ExitStatus 未被元素涵蓋,則框架會拋出異常,且 Job 會失敗。框架會自動將 transitions 從最特定到最不特定排序。這意味著,即使在前面的範例中交換了 stepA 的順序,FAILEDExitStatus 仍然會轉到 stepC

Batch Status 與 Exit Status 的比較

在為條件流程配置 Job 時,了解 BatchStatusExitStatus 之間的區別非常重要。BatchStatus 是一個列舉,它是 JobExecutionStepExecution 的屬性,並由框架用於記錄 JobStep 的狀態。它可以是以下值之一:COMPLETEDSTARTINGSTARTEDSTOPPINGSTOPPEDFAILEDABANDONEDUNKNOWN。它們大多數都是不言自明的:COMPLETED 是步驟或 job 成功完成時設定的狀態,FAILED 是在失敗時設定的狀態,依此類推。

  • Java

  • XML

以下範例包含使用 Java 配置時的 on 元素

...
.from(stepA).on("FAILED").to(stepB)
...

以下範例包含使用 XML 配置時的 next 元素

<next on="FAILED" to="stepB" />

乍看之下,on 似乎引用了它所屬的 StepBatchStatus。然而,它實際上引用了 StepExitStatus。顧名思義,ExitStatus 代表 Step 完成執行後的狀態。

  • Java

  • XML

當使用 Java 配置時,前面 Java 配置範例中顯示的 on() 方法引用了 ExitStatus 的退出代碼。

更具體地說,當使用 XML 配置時,前面 XML 配置範例中顯示的 next 元素引用了 ExitStatus 的退出代碼。

用英文來說,它表示:「如果退出代碼為 FAILED,則轉到 stepB」。預設情況下,退出代碼始終與 StepBatchStatus 相同,這就是為什麼前面的條目有效的原因。但是,如果需要不同的退出代碼怎麼辦?一個很好的範例來自 samples 專案中的 skip 範例 job

  • Java

  • XML

以下範例展示如何在 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 失敗,在這種情況下,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

設定停止

在討論 BatchStatusExitStatus 之後,人們可能會想知道 JobBatchStatusExitStatus 是如何確定的。雖然這些狀態是由執行的程式碼為 Step 確定的,但 Job 的狀態是根據配置確定的。

到目前為止,討論的所有 job 配置都至少有一個沒有 transitions 的最終 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 定義 transitions,則 Job 的狀態定義如下

  • 如果 StepFAILEDExitStatus 結束,則 JobBatchStatusExitStatus 都是 FAILED

  • 否則,JobBatchStatusExitStatus 都是 COMPLETED

雖然這種終止批次 job 的方法對於某些批次 job 來說已經足夠了,例如簡單的循序步驟 job,但可能需要自訂定義的 job 停止情境。為此,Spring Batch 提供了三個 transition 元素來停止 Job(除了我們之前討論過的 next 元素 之外)。這些停止元素中的每一個都會以特定的 BatchStatus 停止 Job。重要的是要注意,停止 transition 元素對 Job 中任何 StepsBatchStatusExitStatus 都沒有影響。這些元素僅影響 Job 的最終狀態。例如,job 中的每個步驟都可能具有 FAILED 狀態,但 job 可能具有 COMPLETED 狀態。

在步驟結束

配置步驟結束指示 JobCOMPLETEDBatchStatus 停止。以 COMPLETED 狀態完成的 Job 無法重新啟動(框架會拋出 JobInstanceAlreadyCompleteException)。

  • Java

  • XML

當使用 Java 配置時,end 方法用於此任務。end 方法還允許使用可選的 exitStatus 參數,您可以使用它來自訂 JobExitStatus。如果沒有提供 exitStatus 值,則 ExitStatus 預設為 COMPLETED,以匹配 BatchStatus

當使用 XML 配置時,您可以使用 end 元素來完成此任務。end 元素還允許使用可選的 exit-code 屬性,您可以使用它來自訂 JobExitStatus。如果未給定 exit-code 屬性,則 ExitStatus 預設為 COMPLETED,以匹配 BatchStatus

考慮以下情境:如果 step2 失敗,則 JobCOMPLETEDBatchStatusCOMPLETEDExitStatus 停止,且 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">

使步驟失敗

配置步驟在給定點失敗會指示 JobFAILEDBatchStatus 停止。與 end 不同,Job 的失敗不會阻止 Job 被重新啟動。

當使用 XML 配置時,fail 元素還允許使用可選的 exit-code 屬性,可用於自訂 JobExitStatus。如果未給定 exit-code 屬性,則 ExitStatus 預設為 FAILED,以匹配 BatchStatus

考慮以下情境:如果 step2 失敗,則 JobFAILEDBatchStatusEARLY TERMINATIONExitStatus 停止,且 step3 不會執行。否則,執行將移至 step3。此外,如果 step2 失敗且 Job 重新啟動,則執行將再次從 step2 開始。

  • Java

  • XML

以下範例顯示 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">

在給定步驟停止 Job

配置 job 在特定步驟停止會指示 JobSTOPPEDBatchStatus 停止。停止 Job 可以提供處理過程中的暫時中斷,以便操作員可以在重新啟動 Job 之前採取一些操作。

  • Java

  • XML

當使用 Java 配置時,stopAndRestart 方法需要一個 restart 屬性,該屬性指定在重新啟動 Job 時應從哪個步驟恢復執行。

當使用 XML 配置時,stop 元素需要一個 restart 屬性,該屬性指定在重新啟動 Job 時應從哪個步驟恢復執行。

考慮以下情境:如果 step1COMPLETE 結束,則 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 呼叫

Java 設定
@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 指定要使用的 decider 以及所有 transitions

XML 設定
<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,該 Job 以線性方式一次執行一個步驟。除了這種典型風格之外,Spring Batch 還允許使用平行流程配置 job。

  • Java

  • XML

基於 Java 的配置允許您透過提供的建構器配置 splits。如下例所示,split 元素包含一個或多個 flow 元素,在其中可以定義整個單獨的流程。split 元素還可以包含任何先前討論過的 transition 元素,例如 next 屬性或 nextendfail 元素。

@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 元素還可以包含任何先前討論過的 transition 元素,例如 next 屬性或 nextendfail 元素。

<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 範例展示如何將流程宣告為對其他地方定義的流程的引用

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>

定義外部流程的效果(如先前範例所示)是將外部流程中的步驟插入到 job 中,就像它們是內聯宣告的一樣。透過這種方式,許多 job 可以參考相同的範本流程,並將這些範本組合成不同的邏輯流程。這也是分離個別流程的整合測試的好方法。

外部化流程的另一種形式是使用 JobStepJobStep 類似於 FlowStep,但實際上會為指定流程中的步驟建立並啟動單獨的 job 執行。

  • Java

  • XML

以下範例顯示 Java 中 JobStep 的範例

Java 設定
@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 的範例

XML 設定
<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 參數提取器是一種策略,用於確定如何將 StepExecutionContext 轉換為要執行的 JobJobParameters。當您希望針對 job 和步驟的監控和報告擁有更精細的選項時,JobStep 非常有用。使用 JobStep 通常也是回答問題:「我如何在 job 之間建立依賴關係?」的好方法。這是將大型系統分解為較小的模組並控制 job 流程的好方法。