当前版本仍在开发中,尚不被视为稳定版本。最新稳定版请使用 Spring Batch 文档 6.0.2!

高级元数据用法

JobRegistry

JobRegistry 用于跟踪当前上下文中有哪些作业可用,以及这些作业是否能够被 JobOperator 操作。当作业是在别处创建的(例如子上下文中)时, 它也非常适合用于在应用上下文中集中收集这些作业。你还可以使用自定义的 JobRegistry 实现, 来控制已注册作业的名称以及其他属性。框架默认只提供了一个实现, 它基于一个简单的“作业名到作业实例”的映射,即 MapJobRegistry

  • Java

  • XML

使用 @EnableBatchProcessing 时,框架会为你提供一个 MapJobRegistry。 下面的示例展示了如何配置你自己的 JobRegistry

...
@Bean
public JobRegistry jobRegistry() throws Exception {
	return new MyCustomJobRegistry();
}
...

下面的示例展示了如何为 XML 中定义的作业引入一个 JobRegistry

<bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry" />

Spring Batch 提供的 MapJobRegistry 足够智能,能够自动用应用上下文中的所有作业填充自身。 不过,如果你使用的是自定义的 JobRegistry 实现,那么就需要手动把希望由 job operator 操作的作业注册进去。

JobParametersIncrementer

JobOperator 上的大多数方法都顾名思义,更详细的说明可以参考 接口的 Javadoc。 不过,startNextInstance 方法值得特别说明。这个方法总是会启动一个新的 Job 实例。 如果某次 JobExecution 出现了严重问题,而 Job 需要从头重新开始执行, 这个方法就会非常有用。与 JobLauncher 不同, 后者要求提供一组新的 JobParameters 来触发一个新的 JobInstance; 而 startNextInstance 会使用绑定到该 Job 上的 JobParametersIncrementer,强制让这个 Job 进入一个新的实例:

public interface JobParametersIncrementer {

    JobParameters getNext(JobParameters parameters);

}

JobParametersIncrementer 的契约是:给定一个 JobParameters 对象, 通过递增其中必要的值,返回“下一组” JobParameters。 这种策略之所以有用,是因为框架本身并不知道对 JobParameters 做出什么样的改变,才能让它成为“下一个”实例。 例如,如果 JobParameters 中唯一的值是一个日期,而现在需要创建下一个实例, 那么这个值到底应该加一天,还是加一周(例如该作业按周运行)?对于用于标识 Job 的各种数值型参数, 也存在同样的问题,如下例所示:

public class SampleIncrementer implements JobParametersIncrementer {

    public JobParameters getNext(JobParameters parameters) {
        if (parameters==null || parameters.isEmpty()) {
            return new JobParametersBuilder().addLong("run.id", 1L).toJobParameters();
        }
        long id = parameters.getLong("run.id",1L) + 1;
        return new JobParametersBuilder().addLong("run.id", id).toJobParameters();
    }
}

在这个示例中,键为 run.id 的值被用来区分不同的 JobInstance。 如果传入的 JobParameters 为 null,就可以认为该 Job 之前从未运行过, 因此返回其初始状态即可;否则,就取出旧值,将其加一后再返回。

  • Java

  • XML

对于使用 Java 定义的作业,你可以通过构建器提供的 incrementer 方法, 把一个 incrementer 关联到某个 Job,如下所示:

@Bean
public Job footballJob(JobRepository jobRepository) {
    return new JobBuilder("footballJob", jobRepository)
    				 .incrementer(sampleIncrementer())
    				 ...
                     .build();
}

对于使用 XML 定义的作业,你可以通过命名空间中的 incrementer 属性, 把一个 incrementer 关联到某个 Job,如下所示:

<job id="footballJob" incrementer="sampleIncrementer">
    ...
</job>

停止 Job

JobOperator 最常见的使用场景之一,就是以优雅方式停止一个 Job:

Set<Long> executions = jobOperator.getRunningExecutions("sampleJob");
jobOperator.stop(executions.iterator().next());

停止并不是立刻发生的,因为没有办法强制立即终止, 尤其是在当前执行点位于框架无法控制的开发者代码中时,例如某个业务服务调用。 不过,一旦控制权回到框架手中,它就会把当前 StepExecution 的状态设置为 BatchStatus.STOPPED 并保存,然后在结束前对 JobExecution 执行同样的处理。

处理外部中断信号

从 v6.0+ 开始,Spring Batch 提供了一个 JobExecutionShutdownHook, 你可以把它挂接到 JVM 运行时中,以便拦截外部中断信号,并优雅地停止当前作业执行:

Thread springBatchHook = new JobExecutionShutdownHook(jobExecution, jobOperator);
Runtime.getRuntime().addShutdownHook(springBatchHook);

JobExecutionShutdownHook 需要一个要跟踪的 job execution, 以及一个将用于停止该执行的 job operator 引用。

恢复 Job

如果优雅停机没有正确完成(例如 JVM 被突然关闭),Spring Batch 就没有机会正确更新执行状态, 从而无法重启失败的 job execution。在这种情况下,该 job execution 会停留在 STARTED 状态,而这个状态是不可重启的。此时,可以通过 JobOperator API 来恢复它:

JobExecution jobExecution = ...; // get the job execution to recover
jobOperator.recover(jobExecution);
jobOperator.restart(jobExecution);

中止 Job

状态为 FAILED 的 job execution 可以被重启 (前提是该 Job 支持重启)。而状态为 ABANDONED 的 job execution 则不能被框架重启。ABANDONED 状态也会用于 step execution, 用来在一次重启后的 job execution 中将这些 step 标记为可跳过。 如果某个作业正在运行,并遇到一个在前一次失败执行中已被标记为 ABANDONED 的 step, 那么它会直接进入下一个 step (具体由作业流程定义以及 step execution 的退出状态决定)。

如果进程直接死掉了(例如 kill -9 或服务器故障),那么作业当然已经不在运行, 但 JobRepository 并不会知道这一点,因为在进程死亡之前没有任何人通知它。 你必须手动告诉它:这次执行是失败了,还是应被视为已中止 (即把状态改为 FAILEDABANDONED)。 这是一个业务层面的决策,无法自动化完成。只有在作业可重启且你确信重启数据有效时, 才应把状态改为 FAILED