控制 Step 流程

透過將步驟分組到一個擁有 Job 中,需要能夠控制 Job 如何從一個步驟“流轉”到另一個步驟。Step 的失敗不一定意味著 Job 應該失敗。此外,可能有不止一種“成功”型別來決定接下來應該執行哪個 Step。取決於如何配置一組 Steps,某些步驟甚至可能完全不會被處理。

流程定義中的 Step Bean 方法代理

步驟例項在流程定義中必須是唯一的。當一個步驟在流程定義中有多個結果時,重要的是將同一個步驟例項傳遞給流程定義方法(如 start, from 等)。否則,流程執行可能會出現意外行為。

在以下示例中,步驟作為引數注入到流程或 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 元素中定義 transition 元素。其中一種轉換是 next 元素。與 next 屬性一樣,next 元素告訴 Job 下一步要執行哪個 Step。然而,與屬性不同的是,一個給定的 Step 可以有任意數量的 next 元素,並且在失敗情況下沒有預設行為。這意味著,如果使用了 transition 元素,則必須顯式定義 Step 轉換的所有行為。另請注意,單個步驟不能同時具有 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 失敗。框架會自動將 transition 元素按從最具體到最不具體的順序排列。這意味著,即使在前面示例中 stepA 的順序顛倒了,FAILEDExitStatus 仍然會流轉到 stepC

BatchStatus 與 ExitStatus 的區別

為條件流程配置 Job 時,理解 BatchStatusExitStatus 之間的區別非常重要。BatchStatusJobExecutionStepExecution 的一個屬性,是一個列舉型別,框架使用它來記錄 JobStep 的狀態。它可以是以下值之一: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 引用了其所屬 StepBatchStatus。然而,它實際上引用的是 StepExitStatus。顧名思義,ExitStatus 表示 Step 完成執行後的狀態。

  • Java

  • XML

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

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

用白話來說,它表示:“如果退出碼是 FAILED,則轉到 stepB”。預設情況下,步驟的退出碼總是與其 BatchStatus 相同,這就是前面的配置起作用的原因。但是,如果退出碼需要不同怎麼辦?一個很好的例子來自 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 是如何確定的。雖然步驟的狀態是由執行的程式碼決定的,但 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 的狀態定義如下

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

  • 否則,JobBatchStatusExitStatus 都為 COMPLETED

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

在 Step 處結束

配置步驟結束會指示 JobCOMPLETEDBatchStatus 停止。狀態為 COMPLETEDJob 不能重啟(框架會丟擲 JobInstanceAlreadyCompleteException 異常)。

  • Java

  • XML

使用 Java 配置時,end 方法用於此任務。end 方法還允許一個可選的 exitStatus 引數,你可以用它來自定義 JobExitStatus。如果未提供 exitStatus 值,則預設 ExitStatusCOMPLETED,以匹配 BatchStatus

使用 XML 配置時,你可以使用 end 元素來完成此任務。end 元素還允許一個可選的 exit-code 屬性,你可以用它來自定義 JobExitStatus。如果未給定 exit-code 屬性,則預設 ExitStatusCOMPLETED,以匹配 BatchStatus

考慮以下場景:如果 step2 失敗,Job 將以 COMPLETEDBatchStatusCOMPLETEDExitStatus 停止,並且 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 失敗

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

使用 XML 配置時,fail 元素也允許一個可選的 exit-code 屬性,可用於自定義 JobExitStatus。如果未給定 exit-code 屬性,則預設 ExitStatusFAILED,以匹配 BatchStatus

考慮以下場景:如果 step2 失敗,Job 將以 FAILEDBatchStatusEARLY 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">

在給定 Step 停止 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 指定了要使用的決策器以及所有轉換

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。除了這種典型風格外,Spring Batch 還允許 Job 配置並行流程。

  • Java

  • XML

基於 Java 的配置允許你透過提供的構建器配置拆分。如下例所示,split 元素包含一個或多個 flow 元素,其中可以定義完全獨立的流程。split 元素還可以包含任何前面討論過的轉換元素,例如 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 元素還可以包含任何前面討論過的轉換元素,例如 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 轉換為要執行的 JobJobParametersJobStep 在你想要對 Job 和步驟進行更細粒度的監控和報告時非常有用。使用 JobStep 也常常是回答“如何建立 Job 之間的依賴關係?”這個問題的好方法。它是將大型系統分解為更小模組並控制 Job 流程的好方法。