高级元数据使用
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 实现,则需要手动将您希望通过作业操作器操作的作业注册到其中。
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 的值被用来区分不同的 JobInstances。如果传入的 JobParameters 为 null,则可以假定该 Job 从未运行过,因此返回其初始状态。否则,将获取旧值,将其加一后返回。
- Java
- XML
对于用 Java 定义的作业,你可以通过构建器中提供的 incrementer 方法将一个增量器与一个 Job 关联起来,如下所示:
@Bean
public Job footballJob(JobRepository jobRepository) {
return new JobBuilder("footballJob", jobRepository)
.incrementer(sampleIncrementer())
...
.build();
}
对于用 XML 定义的作业,你可以通过命名空间中的 incrementer 属性将一个增量器与一个 Job 关联起来,如下所示:
<job id="footballJob" incrementer="sampleIncrementer">
...
</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 需要跟踪作业执行,同时需要一个作业操作器的引用,该操作器将用于停止执行。
恢复作业
如果优雅关闭未能正确执行(即JVM被突然关闭),Spring Batch将没有机会正确更新执行状态以重新启动失败的作业执行。在这种情况下,作业执行将保持在STARTED状态,该状态不可重启。此时,可以通过JobOperator API恢复此类作业执行:
JobExecution jobExecution = ...; // get the job execution to recover
jobOperator.recover(jobExecution);
jobOperator.restart(jobExecution);
中止作业
状态为 FAILED 的作业执行可以重新启动(前提是该 Job 是可重启的)。而状态为 ABANDONED 的作业执行则无法由框架重新启动。ABANDONED 状态也用于步骤执行,以在重新启动的作业执行中将其标记为可跳过。如果作业正在运行,并且遇到在前一次失败的作业执行中被标记为 ABANDONED 的步骤,它将直接跳转到下一步(根据作业流定义和步骤执行退出状态决定)。
如果进程意外终止(例如使用 kill -9 命令或服务器故障),作业实际上并未运行,但 JobRepository 无法感知这一情况,因为在进程终止前没有任何信息通知它。此时需要手动告知 JobRepository,该执行已失败或应被视为已放弃(将其状态更改为 FAILED 或 ABANDONED)。这是一个业务决策,无法通过自动化实现。仅当作业可重启且确认重启数据有效时,才将其状态更改为 FAILED。