1. 简介

在使用 Spring Batch 构建批处理任务时,我们通常会将一个 Job 拆分为多个 Step,每个 Step 负责数据的读取、转换或写入。但现实场景往往更复杂——Job 的执行路径并不是一条直线,而是像代码中的 if 语句一样存在分支逻辑。

这种根据条件决定下一步执行哪个 Step 的机制,我们称之为 条件流程(Conditional Flow)

本文将介绍两种在 Spring Batch 中实现条件流程的常用方式,帮你避开“所有流程都线性执行”的坑,让 Job 更加灵活可控。


2. Exit Status 与 Batch Status 的区别

在设计条件流程前,必须搞清楚两个核心概念:Batch StatusExit Status。它们名字相似,用途却完全不同,混淆它们是新手常踩的坑 ❌。

✅ Batch Status

  • 是 Spring Batch 内部用于表示 Step 或 Job 当前运行状态的枚举(BatchStatus
  • 常见取值包括:COMPLETED, FAILED, STARTED, STOPPED, ABANDONED
  • 它反映的是“执行状态”,比如是否成功完成、是否失败等

✅ Exit Status

  • 是 Step 执行结束后对外输出的状态,用于驱动流程跳转
  • 类型为 ExitStatus,可自定义值(如 NOTIFY, SKIP, LOG_ERROR
  • 默认情况下,ExitStatus 会继承 BatchStatus,但我们可以通过代码手动覆盖

⚠️ 关键点:**条件流程是基于 ExitStatus 判断的,而不是 BatchStatus**。这是很多开发者一开始搞错的地方。


3. 条件流程实现方式

假设我们有一个 IoT 设备定期上传整数数组形式的测量数据。需求是:只要数据中包含任意正数,就要触发通知

这就需要我们在批处理 Job 中引入条件判断。下面介绍两种实现思路。


3.1 使用 ExitStatus 控制流程

这是最常见也最直接的方式:通过设置 Step 的 ExitStatus 来决定后续流程走向

核心机制

  • ItemProcessorItemReaderItemWriter 中获取 StepExecution
  • 调用 stepExecution.setExitStatus(new ExitStatus("自定义状态"))
  • 在 Job 配置中通过 .on("状态") 匹配并跳转

示例:检测正数并设置 ExitStatus

public class NumberInfoClassifier extends ItemListenerSupport<NumberInfo, Integer>
    implements ItemProcessor<NumberInfo, Integer> {

    private StepExecution stepExecution;

    @BeforeStep
    public void beforeStep(StepExecution stepExecution) {
        this.stepExecution = stepExecution;
        // 初始化为 QUIET,表示无需通知
        this.stepExecution.setExitStatus(new ExitStatus("QUIET"));
    }

    @Override
    public Integer process(NumberInfo numberInfo) throws Exception {
        return Integer.valueOf(numberInfo.getNumber());
    }

    @Override
    public void afterProcess(NumberInfo item, Integer result) {
        super.afterProcess(item, result);
        // 如果发现正数,修改 ExitStatus 为 NOTIFY
        if (item.isPositive()) {
            stepExecution.setExitStatus(new ExitStatus("NOTIFY"));
        }
    }
}

✅ 提示:虽然这里用了 ItemProcessor,但你也可以在 ItemReaderItemWriter 中做类似操作,取决于你的业务逻辑放在哪一层。

配置 Job 流程

new JobBuilder("Number generator - second dataset", jobRepository)
    .start(dataProviderStep)
    .on("NOTIFY").to(notificationStep)  // 如果 ExitStatus 是 NOTIFY,跳转到通知步骤
    .end()
    .build();

多分支场景

当存在多个条件分支时,可以用 .from() 链式配置:

new JobBuilder("Number generator - second dataset", jobRepository)
    .start(dataProviderStep)
    .on("NOTIFY").to(notificationStep)
    .from(dataProviderStep).on("LOG_ERROR").to(errorLoggingStep)
    .end()
    .build();

执行效果对比

  • 有正数时

    Second Dataset Processor 11
    Second Dataset Processor -2
    Second Dataset Processor -3
    [Number generator - second dataset] contains interesting data!!
    

    → 触发了 notificationStep

  • 无正数时

    Second Dataset Processor -1
    Second Dataset Processor -2
    Second Dataset Processor -3
    

    → 没有输出通知,流程正常结束


3.2 使用 JobExecutionDecider 实现程序化分支

上面的方法适合基于数据内容做判断。但如果决策逻辑依赖外部系统(如数据库、配置中心、API 调用结果),那就更适合使用 JobExecutionDecider

适用场景

  • 分支逻辑与 Step 内部数据无关
  • 需要调用外部服务或检查全局状态
  • 想把流程控制逻辑独立出来,提升可测试性

示例:自定义 Decider

先简化 ItemProcessor,不再处理状态:

public class NumberInfoClassifierWithDecider implements ItemProcessor<NumberInfo, Integer> {

    @Override
    public Integer process(NumberInfo numberInfo) throws Exception {
        return Integer.valueOf(numberInfo.getNumber());
    }
}

然后创建一个 JobExecutionDecider 实现类:

public class NumberInfoDecider implements JobExecutionDecider {

    private boolean shouldNotify() {
        // 这里可以接入配置中心、数据库查询等外部逻辑
        return true; // 简化示例,实际中可能是动态判断
    }

    @Override
    public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
        if (shouldNotify()) {
            return new FlowExecutionStatus("NOTIFY");
        } else {
            return new FlowExecutionStatus("QUIET");
        }
    }
}

配置 Job 使用 Decider

new JobBuilder("Number generator - third dataset", jobRepository)
    .start(dataProviderStep)
    .next(new NumberInfoDecider())     // 执行完 dataProviderStep 后进入 Decider
    .on("NOTIFY").to(notificationStep) // 根据返回状态跳转
    .end()
    .build();

✅ 优势:逻辑解耦,Decider 可以轻松 mock 测试,适合复杂判断场景。


4. 总结

方式 适用场景 特点
ExitStatus 基于 Step 内部数据做判断 简单粗暴,适合数据驱动的流程控制
JobExecutionDecider 外部条件或复杂逻辑判断 解耦清晰,易于测试,适合配置化分支

选择哪种方式,关键看你的判断依据来自哪里

  • 数据在流中 → 用 ExitStatus
  • 判断依赖外部系统 → 用 JobExecutionDecider

💡 小技巧:两者也可以结合使用。比如先用 ItemProcessor 设置 ExitStatus,再用 Decider 做二次校验。


附录:完整源码

本文所有示例代码均已开源,可在 GitHub 获取:

👉 https://github.com/baeldung/spring-batch/tree/main/spring-batch-conditional

邮箱联系:support@baeldung.com


原始标题:Conditional Flow in Spring Batch