目录
引言
多步骤执行
步骤控制
默认分支控制
决策器分支控制
流式步骤
转视频版
接着上篇:Spring Batch 步骤对象-步骤监听器,了解step步骤监听器后,本篇再来讲解spring batch步骤控制,看批处理中的步骤控制能玩出啥花来。
到目前为止,我们演示的案例基本上都是一个作业, 一个步骤,那如果有多个步骤会怎样?Spring Batch 支持多步骤执行,以应对复杂业务需要多步骤配合执行的场景。
需求:定义2个步骤,然后依次执行
package com.langfeiyes.batch._10_step_multi;
import com.langfeiyes.batch._09_step_listener.MyChunkListener;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableBatchProcessing
public class MultiStepJob {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Tasklet tasklet1(){
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("--------------tasklet1---------------");
return RepeatStatus.FINISHED;
}
};
}
@Bean
public Tasklet tasklet2(){
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("--------------tasklet2---------------");
return RepeatStatus.FINISHED;
}
};
}
@Bean
public Step step1(){
return stepBuilderFactory.get("step1")
.tasklet(tasklet1())
.build();
}
@Bean
public Step step2(){
return stepBuilderFactory.get("step2")
.tasklet(tasklet2())
.build();
}
//定义作业
@Bean
public Job job(){
return jobBuilderFactory.get("step-multi-job1")
.start(step1())
.next(step2()) //job 使用next 执行下一步骤
.incrementer(new RunIdIncrementer())
.build();
}
public static void main(String[] args) {
SpringApplication.run(MultiStepJob.class, args);
}
}
定义2个tasklet: tasklet1 tasklet2, 定义2个step: step1 step2 修改 job方法,从.start(step1()) 然后执行到 .next(step2())
Spring Batch 使用next 执行下一步步骤,如果还有第三个step,再加一个next(step3)即可
上面多个步骤操作,先执行step1 然后是step2,如果有step3, step4,那执行顺序也是从step1到step4。此时爱思考的小伙伴肯定会想,步骤的执行能不能进行条件控制呢?比如:step1执行结束根据业务条件选择执行step2或者执行step3,亦或者直接结束呢?答案是yes:设置步骤执行条件即可
Spring Batch 使用 start next on from to end 不同的api 改变步骤执行顺序。
需求:作业执行firstStep步骤,如果处理成功执行sucessStep,如果处理失败执行failStep
package com.langfeiyes.batch._11_step_condition;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableBatchProcessing
public class ConditionStepJob {
@Autowired
private JobLauncher jobLauncher;
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Tasklet firstTasklet(){
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("--------------firstTasklet---------------");
return RepeatStatus.FINISHED;
//throw new RuntimeException("测试fail结果");
}
};
}
@Bean
public Tasklet successTasklet(){
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("--------------successTasklet---------------");
return RepeatStatus.FINISHED;
}
};
}
@Bean
public Tasklet failTasklet(){
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("--------------failTasklet---------------");
return RepeatStatus.FINISHED;
}
};
}
@Bean
public Step firstStep(){
return stepBuilderFactory.get("step1")
.tasklet(firstTasklet())
.build();
}
@Bean
public Step successStep(){
return stepBuilderFactory.get("successStep")
.tasklet(successTasklet())
.build();
}
@Bean
public Step failStep(){
return stepBuilderFactory.get("failStep")
.tasklet(failTasklet())
.build();
}
//定义作业
@Bean
public Job job(){
return jobBuilderFactory.get("condition-multi-job")
.start(firstStep())
.on("FAILED").to(failStep())
.from(firstStep()).on("*").to(successStep())
.end()
.incrementer(new RunIdIncrementer())
.build();
}
public static void main(String[] args) {
SpringApplication.run(ConditionStepJob.class, args);
}
}
观察给出的案例,job方法以 .start(firstStep()) 开始作业,执行完成之后, 使用on 与from 2个方法实现流程转向。
.on("FAILED").to(failStep()) 表示当firstStep()返回FAILED时执行。
.from(firstStep()).on("*").to(successStep()) 另外一个分支,表示当firstStep()返回 * 时执行。
上面逻辑有点像 if / else 语法
if("FAILED".equals(firstStep())){
failStep();
}else{
successStep();
}
几个注意点:
1> on 方法表示条件, 上一个步骤返回值,匹配指定的字符串,满足后执行后续 to 步骤
2> * 为通配符,表示能匹配任意返回值
3> from 表示从某个步骤开始进行条件判断
4> 分支判断结束,流程以end方法结束,表示if/else逻辑结束
5> on 方法中字符串取值于 ExitStatus 类常量,当然也可以自定义。
前面也说了,on条件的值取值于ExitStatus 类常量,具体值有:UNKNOWN,EXECUTING,COMPLETED,NOOP,FAILED,STOPPED等,如果此时我想自定义返回值呢,是否可行?答案还是yes:Spring Batch 提供JobExecutionDecider 接口实现状态值定制。
需求:先执行firstStep,如果返回值为A,执行stepA, 返回值为B,执行stepB, 其他执行defaultStep
分析:先定义一个决策器,随机决定返回A / B / C
public class MyStatusDecider implements JobExecutionDecider {
@Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
long ret = new Random().nextInt(3);
if(ret == 0){
return new FlowExecutionStatus("A");
}else if(ret == 1){
return new FlowExecutionStatus("B");
}else{
return new FlowExecutionStatus("C");
}
}
}
package com.langfeiyes.batch._11_step_condition_decider;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableBatchProcessing
public class CustomizeStatusStepJob {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Tasklet taskletFirst(){
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("--------------taskletFirst---------------");
return RepeatStatus.FINISHED;
}
};
}
@Bean
public Tasklet taskletA(){
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("--------------taskletA---------------");
return RepeatStatus.FINISHED;
}
};
}
@Bean
public Tasklet taskletB(){
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("--------------taskletB---------------");
return RepeatStatus.FINISHED;
}
};
}
@Bean
public Tasklet taskletDefault(){
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("--------------taskletDefault---------------");
return RepeatStatus.FINISHED;
}
};
}
@Bean
public Step firstStep(){
return stepBuilderFactory.get("firstStep")
.tasklet(taskletFirst())
.build();
}
@Bean
public Step stepA(){
return stepBuilderFactory.get("stepA")
.tasklet(taskletA())
.build();
}
@Bean
public Step stepB(){
return stepBuilderFactory.get("stepB")
.tasklet(taskletB())
.build();
}
@Bean
public Step defaultStep(){
return stepBuilderFactory.get("defaultStep")
.tasklet(taskletDefault())
.build();
}
//决策器
@Bean
public MyStatusDecider statusDecider(){
return new MyStatusDecider();
}
//定义作业
@Bean
public Job job(){
return jobBuilderFactory.get("customize-step-job")
.start(firstStep())
.next(statusDecider())
.from(statusDecider()).on("A").to(stepA())
.from(statusDecider()).on("B").to(stepB())
.from(statusDecider()).on("*").to(defaultStep())
.end()
.incrementer(new RunIdIncrementer())
.build();
}
public static void main(String[] args) {
SpringApplication.run(CustomizeStepJob.class, args);
}
}
反复执行,会返回打印的值有
--------------taskletA---------------
--------------taskletB---------------
--------------taskletDefault---------------
它们随机切换,为啥能做到这样?注意,并不是firstStep() 执行返回值为A/B/C控制流程跳转,而是由后面.next(statusDecider()) 决策器。
除去上面的步骤顺序执行,还有一种流式步骤,也可以理解嵌套步骤,或集合步骤。FlowStep 流式步骤,由多个子步骤组成。作业执行时,将它当做一个普通步骤执行。一般用于较为复杂的业务,比如:一个业务逻辑需要拆分成按顺序执行的子步骤。
需求:先后执行stepA,stepB,stepC, 其中stepB中包含stepB1, stepB2,stepB3。
package com.langfeiyes.batch._13_flow_step;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.builder.FlowBuilder;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.job.builder.SimpleJobBuilder;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableBatchProcessing
public class FlowStepJob {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Bean
public Tasklet taskletA(){
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("------------stepA--taskletA---------------");
return RepeatStatus.FINISHED;
}
};
}
@Bean
public Tasklet taskletB1(){
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("------------stepB--taskletB1---------------");
return RepeatStatus.FINISHED;
}
};
}
@Bean
public Tasklet taskletB2(){
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("------------stepB--taskletB2---------------");
return RepeatStatus.FINISHED;
}
};
}
@Bean
public Tasklet taskletB3(){
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("------------stepB--taskletB3---------------");
return RepeatStatus.FINISHED;
}
};
}
@Bean
public Tasklet taskletC(){
return new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("------------stepC--taskletC---------------");
return RepeatStatus.FINISHED;
}
};
}
@Bean
public Step stepA(){
return stepBuilderFactory.get("stepA")
.tasklet(taskletA())
.build();
}
@Bean
public Step stepB1(){
return stepBuilderFactory.get("stepB1")
.tasklet(taskletB1())
.build();
}
@Bean
public Step stepB2(){
return stepBuilderFactory.get("stepB2")
.tasklet(taskletB2())
.build();
}
@Bean
public Step stepB3(){
return stepBuilderFactory.get("stepB3")
.tasklet(taskletB3())
.build();
}
@Bean
public Flow flowB(){
return new FlowBuilder("flowB")
.start(stepB1())
.next(stepB2())
.next(stepB3())
.build();
}
@Bean
public Step stepB(){
return stepBuilderFactory.get("stepB")
.flow(flowB())
.build();
}
@Bean
public Step stepC(){
return stepBuilderFactory.get("stepC")
.tasklet(taskletC())
.build();
}
//定义作业
@Bean
public Job job(){
return jobBuilderFactory.get("flow-step-job")
.start(stepA())
.next(stepB())
.next(stepC())
.incrementer(new RunIdIncrementer())
.build();
}
public static void main(String[] args) {
SpringApplication.run(FlowStepJob.class, args);
}
}
此时的flowB()就是一个FlowStep,包含了stepB1, stepB2, stepB3 3个子step,他们全部执行完后, stepB才能算执行完成。下面执行结果也验证了这点。
2022-12-03 14:54:16.644 INFO 19116 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [stepA]
------------stepA--taskletA---------------
2022-12-03 14:54:16.699 INFO 19116 --- [ main] o.s.batch.core.step.AbstractStep : Step: [stepA] executed in 55ms
2022-12-03 14:54:16.738 INFO 19116 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [stepB]
2022-12-03 14:54:16.788 INFO 19116 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [stepB1]
------------stepB--taskletB1---------------
2022-12-03 14:54:16.844 INFO 19116 --- [ main] o.s.batch.core.step.AbstractStep : Step: [stepB1] executed in 56ms
2022-12-03 14:54:16.922 INFO 19116 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [stepB2]
------------stepB--taskletB2---------------
2022-12-03 14:54:16.952 INFO 19116 --- [ main] o.s.batch.core.step.AbstractStep : Step: [stepB2] executed in 30ms
2022-12-03 14:54:16.996 INFO 19116 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [stepB3]
------------stepB--taskletB3---------------
2022-12-03 14:54:17.032 INFO 19116 --- [ main] o.s.batch.core.step.AbstractStep : Step: [stepB3] executed in 36ms
2022-12-03 14:54:17.057 INFO 19116 --- [ main] o.s.batch.core.step.AbstractStep : Step: [stepB] executed in 318ms
2022-12-03 14:54:17.165 INFO 19116 --- [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [stepC]
------------stepC--taskletC---------------
2022-12-03 14:54:17.215 INFO 19116 --- [ main] o.s.batch.core.step.AbstractStep : Step: [stepC] executed in 50ms
使用FlowStep的好处在于,在处理复杂额批处理逻辑中,flowStep可以单独实现一个子步骤流程,为批处理提供更高的灵活性。
到这,本篇就结束了,欲知后事如何,请听下回分解~
看文字不过瘾可以切换视频版:Spring Batch高效批处理框架实战