跳到主要内容

运行作业

QWen Plus 中英对照 Running a Job

至少,启动批处理作业需要两件事:要启动的 Job 和一个 JobLauncher。这两者可以包含在同一个上下文中,也可以包含在不同的上下文中。例如,如果从命令行启动作业,则为每个 Job 实例化一个新的 JVM。因此,每个作业都有自己的 JobLauncher。然而,如果在 Web 容器内运行,并且处于 HttpRequest 的范围内,通常只有一个 JobLauncher(配置为异步启动作业),多个请求会调用它来启动各自的作业。

从命令行运行作业

如果你希望从企业调度器运行你的任务,命令行是主要的接口。这是因为大多数调度器(Quartz 除外,除非使用 NativeJob)直接与操作系统进程交互,通常通过 shell 脚本来启动。除了 shell 脚本,还有许多方法可以启动 Java 进程,例如 Perl、Ruby 或者甚至是构建工具,如 Ant 或 Maven。然而,由于大多数人对 shell 脚本比较熟悉,所以这个例子将重点放在它们上面。

命令行作业运行器

因为启动任务的脚本需要启动一个 Java 虚拟机,所以必须有一个包含 main 方法的类作为主要入口点。Spring Batch 提供了一个实现该功能的类:CommandLineJobRunner。需要注意的是,这仅仅是引导应用程序的其中一种方式。启动 Java 进程的方法有很多种,这个类绝不应该被视为唯一确定的方式。CommandLineJobRunner 执行以下四项任务:

  • 加载适当的 ApplicationContext

  • 将命令行参数解析为 JobParameters

  • 根据参数定位合适的作业。

  • 使用应用程序上下文中提供的 JobLauncher 来启动作业。

所有这些任务仅通过传递的参数来完成。下表描述了所需的参数:

表 1. CommandLineJobRunner 参数

jobPath用于创建 ApplicationContext 的 XML 文件的位置。此文件应包含运行完整 Job 所需的一切内容。
jobName要运行的作业的名称。

这些参数必须按顺序传入,路径在前,名称在后。在这两个参数之后的所有参数都将被视为作业参数,会被转换为 JobParameters 对象,并且必须采用 name=value 的格式。

下面的示例展示了将日期作为作业参数传递给用 Java 定义的作业:

<bash$ java CommandLineJobRunner io.spring.EndOfDayJobConfiguration endOfDay schedule.date=2007-05-05,java.time.LocalDate
none
备注

默认情况下,CommandLineJobRunner 使用 DefaultJobParametersConverter,它会隐式地将键/值对转换为标识作业参数。但是,您可以通过分别在参数后添加 truefalse 后缀,显式指定哪些作业参数是标识性的,哪些不是。

在以下示例中,schedule.date 是一个标识作业参数,而 vendor.id 不是:

<bash$ java CommandLineJobRunner endOfDayJob.xml endOfDay \
schedule.date=2007-05-05,java.time.LocalDate,true \
vendor.id=123,java.lang.Long,false
none
<bash$ java CommandLineJobRunner io.spring.EndOfDayJobConfiguration endOfDay \
schedule.date=2007-05-05,java.time.LocalDate,true \
vendor.id=123,java.lang.Long,false
none

您可以使用自定义的 JobParametersConverter 来覆盖此行为。

在大多数情况下,您会希望使用清单来声明 jar 文件中的 main 类。然而,为了简化操作,这里直接使用了类。此示例使用了 批量处理的领域语言 中的 EndOfDay 示例。第一个参数是 io.spring.EndOfDayJobConfiguration,这是包含 Job 的配置类的全限定类名。第二个参数 endOfDay 表示作业名称。最后一个参数 schedule.date=2007-05-05,java.time.LocalDate 将被转换为类型为 java.time.LocalDateJobParameter 对象。

下面的示例展示了 Java 中 endOfDay 的一个样例配置:

@Configuration
@EnableBatchProcessing
public class EndOfDayJobConfiguration {

@Bean
public Job endOfDay(JobRepository jobRepository, Step step1) {
return new JobBuilder("endOfDay", jobRepository)
.start(step1)
.build();
}

@Bean
public Step step1(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
return new StepBuilder("step1", jobRepository)
.tasklet((contribution, chunkContext) -> null, transactionManager)
.build();
}
}
java

前面的例子过于简单化,因为通常在 Spring Batch 中运行批处理作业还有许多其他要求,但它足以展示 CommandLineJobRunner 的两个主要要求:JobJobLauncher

退出代码

在从命令行启动批处理作业时,通常会使用企业调度器。大多数调度器相对比较简单,仅在进程级别工作。这意味着它们只知道某些操作系统进程(例如它们调用的 shell 脚本)。在这种情况下,向调度器反馈作业成功或失败的唯一方式是通过返回码。返回码是一个由进程返回给调度器的数字,用于表示运行的结果。最简单的情况下,0 表示成功,1 表示失败。然而,可能会有更复杂的情况,例如“如果作业 A 返回 4,则启动作业 B;如果它返回 5,则启动作业 C”。这种类型的行为是在调度器级别配置的,但重要的是,像 Spring Batch 这样的处理框架需要提供一种方法来为特定的批处理作业返回退出代码的数值表示。在 Spring Batch 中,这是封装在 ExitStatus 内的,将在第 5 章中更详细地介绍。就讨论退出代码而言,唯一需要了解的重要内容是 ExitStatus 具有一个退出代码属性,该属性由框架(或开发人员)设置,并作为从 JobLauncher 返回的 JobExecution 的一部分返回。CommandLineJobRunner 使用 ExitCodeMapper 接口将此字符串值转换为数字:

public interface ExitCodeMapper {

public int intValue(String exitCode);

}
java

ExitCodeMapper 的核心契约是,给定一个字符串形式的退出码,将返回其数字表示。作业运行器使用的默认实现是 SimpleJvmExitCodeMapper,它为完成返回 0,为通用错误返回 1,为作业运行器错误(例如在提供的上下文中无法找到 Job)返回 2。如果需要比上述三个值更复杂的映射,则必须提供 ExitCodeMapper 接口的自定义实现。由于 CommandLineJobRunner 是创建 ApplicationContext 的类,因此无法进行 “连线” 配置,任何需要覆盖的值都必须通过自动装配实现。这意味着,如果在 BeanFactory 中找到了 ExitCodeMapper 的实现,在上下文创建后,它将被注入到运行器中。要提供自己的 ExitCodeMapper,只需将其实现声明为根级别的 Bean,并确保它是运行器加载的 ApplicationContext 的一部分即可。

从 Web 容器内运行作业

历史上,离线处理(例如批处理任务)通常从命令行启动,这一点前面已经提到。然而,在许多情况下,通过 HttpRequest 启动任务是一个更好的选择。这类使用场景包括报表生成、临时任务运行以及 Web 应用程序支持。由于批处理任务(按定义)是长时间运行的,因此最重要的问题是需要异步启动该任务:

从Web容器异步任务启动序列

图 1. 来自 Web 容器的异步作业启动序列

在这种情况下,控制器是一个 Spring MVC 控制器。更多关于 Spring MVC 的内容,请参阅 Spring Framework 参考指南。该控制器通过使用已配置为以[异步方式](#runningJobsFromWebContainer)启动的 JobLauncher 来启动一个 Job,这会立即返回一个 JobExecution。此时,Job 很可能仍在运行。然而,这种非阻塞行为允许控制器立即返回,这是在处理 HttpRequest 时所必需的。以下代码示例展示了这一点:

@Controller
public class JobLauncherController {

@Autowired
JobLauncher jobLauncher;

@Autowired
Job job;

@RequestMapping("/jobLauncher.html")
public void handle() throws Exception{
jobLauncher.run(job, new JobParameters());
}
}
java