控制 Step 流程

透過將步驟分組到一個所有者作業中,需要能夠控制作業如何從一個步驟“流”向另一個步驟。一個Step的失敗不一定意味著Job應該失敗。此外,可能存在不止一種“成功”型別,決定接下來應該執行哪個Step。根據一組Steps的配置方式,某些步驟甚至可能根本不被處理。

流定義中的步驟 Bean 方法代理

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

在以下示例中,步驟作為引數注入到流或作業 Bean 定義方法中。這種依賴注入方式保證了流定義中步驟的唯一性。然而,如果流是透過呼叫帶有@Bean註解的步驟定義方法來定義的,那麼如果 Bean 方法代理被停用(即@Configuration(proxyBeanMethods = false)),步驟可能不是唯一的。如果偏好使用 Bean 間注入方式,那麼必須啟用 Bean 方法代理。

有關 Spring Framework 中 Bean 方法代理的更多詳細資訊,請參閱使用 @Configuration 註解部分。

順序流程

最簡單的流程場景是所有步驟按順序執行的作業,如下圖所示

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 元素。其中一個 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 配置時,過渡元素的on屬性使用簡單的模式匹配方案來匹配Step執行結果的ExitStatus

模式中只允許兩個特殊字元

  • *匹配零個或多個字元

  • ?精確匹配一個字元

例如,c*t匹配catcount,而c?t匹配cat但不匹配count

雖然Step上的轉換元素數量沒有限制,但如果Step執行結果的ExitStatus未被任何元素覆蓋,框架將丟擲異常,並且Job將失敗。框架會自動將轉換從最具體到最不具體的順序排列。這意味著,即使stepA在上述示例中的順序被交換,FAILEDExitStatus仍會轉到stepC

批處理狀態與退出狀態

在為條件流配置Job時,理解BatchStatusExitStatus之間的區別很重要。BatchStatus是一個列舉,它是JobExecutionStepExecution的屬性,由框架用於記錄JobStep的狀態。它可以是以下值之一:COMPLETEDSTARTINGSTARTEDSTOPPINGSTOPPEDFAILEDABANDONEDUNKNOWN。它們大多不言自明:當步驟或作業成功完成時設定COMPLETED狀態,當它失敗時設定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相同,這就是前面條目有效的原因。但是,如果退出程式碼需要不同怎麼辦?一個很好的例子來自示例專案中的跳過示例作業

  • 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失敗,在這種情況下,作業應該失敗。

  • 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的狀態是根據配置確定的。

到目前為止,所有討論的作業配置都至少有一個沒有過渡的最終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

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

在步驟處結束

配置步驟結束指示JobCOMPLETEDBatchStatus停止。已完成狀態為COMPLETEDJob無法重新啟動(框架會丟擲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停止。與結束不同,Job的失敗不會阻止Job重新啟動。

使用 XML 配置時,fail元素還允許一個可選的exit-code屬性,該屬性可用於自定義JobExitStatus。如果沒有給定exit-code屬性,ExitStatus預設情況下為FAILED,以匹配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">

在給定步驟處停止作業

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

  • Java

  • XML

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

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

考慮以下場景:如果step1COMPLETE結束,則作業停止。一旦重新啟動,執行將從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();
}

在以下示例作業配置中,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"/>

拆分流程

到目前為止所描述的每個場景都涉及一個作業,該作業以線性方式一次執行一個步驟。除了這種典型樣式之外,Spring Batch 還允許配置具有並行流的作業。

  • 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"/>

外部化流程定義和作業之間的依賴關係

作業中的一部分流程可以作為單獨的 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>

如上例所示,定義外部流的效果是:將外部流中的步驟插入到作業中,就像它們是內聯宣告的一樣。透過這種方式,許多作業可以引用相同的模板流,並將這些模板組合成不同的邏輯流。這也是分離各個流的整合測試的好方法。

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

  • 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>

作業引數提取器是一種策略,它決定如何將StepExecutionContext轉換為要執行的JobJobParameters。當您希望對作業和步驟進行更細粒度的監控和報告選項時,JobStep非常有用。使用JobStep通常也是回答“如何建立作業之間的依賴關係?”這個問題的很好答案。這是將大型系統分解為更小的模組並控制作業流程的好方法。

© . This site is unofficial and not affiliated with VMware.