Running a Job

Batch Processing, Exit Codes, JobExecution, ExitCodeMapper, CommandLine, Web Container, Spring MVC :description: Spring Batch 提供了两种方法来启动批处理作业:从命令行和 Web 容器中。从命令行启动作业需要一个 CommandLineJobRunner,该 runner 加载 ApplicationContext、解析命令行参数、找到作业并使用 JobLauncher 启动作业。从 Web 容器启动作业涉及一个异步 JobLauncher,该 JobLauncher 在 HttpRequest 范围内运行,在控制器中使用 JobLauncher 启动作业,该 JobLauncher 立即返回 JobExecution。

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

Running Jobs from the Command Line

如果你想从企业计划程序运行作业,则命令行是主要接口。这是因为大多数计划程序(Quartz 除外,除非使用 NativeJob)直接与操作系统进程一起工作,主要是用 shell 脚本启动的。除了 shell 脚本之外,还有许多方法可以启动 Java 进程,例如 Perl、Ruby,甚至构建工具,例如 Ant 或 Maven。然而,由于大多数人熟悉 shell 脚本,所以本示例重点关注它们。

The CommandLineJobRunner

由于启动作业的脚本必须启动 Java Virtual Machine,因此需要有一个带有 main 方法的类充当主要入口点。Spring Batch 提供了一个实现来实现此目的:CommandLineJobRunner。请注意,这只是引导应用程序的一种方式。有许多方法可以启动 Java 进程,此类决不应被视为明确的。CommandLineJobRunner 执行四项任务:

  • Load the appropriate ApplicationContext.

  • 将命令行参数解析为 JobParameters

  • 根据参数找到适当的工作。

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

所有这些任务仅使用传入的参数完成。下表描述了必需的参数:

Table 1. CommandLineJobRunner arguments

jobPath

用于创建 ApplicationContext 的 XML 文件的位置。此文件应包含运行完整 Job 所需的一切内容。

jobName

要运行作业的名称。

必须传入这些参数,先输入路径,然后再输入名称。这些之后的所有参数都被视为作业参数,变为一个 JobParameters 对象,并且必须采用 name=value 格式。

Java

以下示例显示了作为作业参数传递给在 Java 中定义的作业的日期:

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

以下范例显示一个日期传递为作业参数到 XML 中定义的作业:

<bash$ java CommandLineJobRunner endOfDayJob.xml endOfDay schedule.date=2007-05-05,java.time.LocalDate

默认情况下,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
<bash$ java CommandLineJobRunner io.spring.EndOfDayJobConfiguration endOfDay \
                                 schedule.date=2007-05-05,java.time.LocalDate,true \
                                 vendor.id=123,java.lang.Long,false

你可以使用自定义 JobParametersConverter 覆盖此行为。

Java

在大多数情况下,你都希望使用清单在 jar 中声明 main 类。但是,为简单起见,直接使用了该类。此示例从 The Domain Language of Batch 中使用了 EndOfDay 示例。第一个参数是 io.spring.EndOfDayJobConfiguration,它是包含 Job 的配置类中完全限定的类名称。第二个参数 endOfDay 表示 job 名称。最终参数 schedule.date=2007-05-05,java.time.LocalDate 转换为类型 java.time.LocalDateJobParameter 对象。 以下范例显示一个 EndOfDay 配置范例在 Java 中:

@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();
    }
}
XML

在大多数情况下,你都希望使用清单在 jar 中声明 main 类。但是,由于简单起见,直接使用了该类。此示例从 The Domain Language of Batch 中使用了 EndOfDay 示例。第一个参数是 endOfDayJob.xml,它是包含 Job 的 Spring ApplicationContext。第二个参数 endOfDay, 表示 job 名称。最终参数 schedule.date=2007-05-05,java.time.LocalDate 转换为类型为 java.time.LocalDateJobParameter 对象。 以下范例显示一个 EndOfDay 配置范例在 XML 中:

<job id="endOfDay">
    <step id="step1" parent="simpleStep" />
</job>

<!-- Launcher details removed for clarity -->
<beans:bean id="jobLauncher"
         class="org.springframework.batch.core.launch.support.TaskExecutorJobLauncher" />

前面的范例过于简单,因为一般在 Spring Batch 中运行批处理作业有许多更多需求,但它有助于展示 CommandLineJobRunner 的两个主要需求:JobJobLauncher

Exit Codes

从命令行启动批处理作业的时候,通常会使用企业调度程序。大多数调度程序相当愚蠢,并且仅在进程级别工作。这意味着它们只了解某些操作系统进程(如它们调用的 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);

}

ExitCodeMapper 的重要契约是,给定一个字符串退出码,将返回一个数字表示形式。作业运行器使用的默认实现是 SimpleJvmExitCodeMapper,如果完成则返回 0,如果有通用错误则返回 1,如果有任何作业运行器错误(如在所提供的上下文中找不到 Job)则返回 2。如果需要比上面三个值更复杂的东西,则必须提供 ExitCodeMapper 接口的自定义实现。因为 CommandLineJobRunner 是创建 ApplicationContext 的类,因此无法“接线”,任何需要覆盖的值都必须自动连接。这意味着如果在 BeanFactory 中找到 ExitCodeMapper 实现,则会在创建该上下文后将其注入到运行器中。所需要做的就是提供你自己的 ExitCodeMapper,声明该实现作为一个根级别 Bean,并确保它是运行器加载的 ApplicationContext 的一部分。

Running Jobs from within a Web Container

从历史上看,离线处理(例如批处理作业)已经从命令行启动,如前所述。然而,在许多情况下,从 HttpRequest 启动是一个更好的选择。许多这样的用例包括报告、即席作业运行和 Web 应用程序支持。因为批处理作业(根据定义)是长时间运行的,所以最重要的考虑是异步地启动作业:

launch from request
Figure 1. Asynchronous Job Launcher Sequence From Web Container

在这种情况下,控制器是 Spring MVC 控制器。有关 Spring MVC 的更多信息,请参阅 Spring 框架参考指南。该控制器使用已配置为启动 asynchronouslyJobLauncher 启动 Job,从而立即返回 JobExecutionJob 仍可能在运行。但是,此非阻塞行为使控制器可以立即返回,这是处理 HttpRequest 时必需的。以下清单显示了一个示例:

@Controller
public class JobLauncherController {

    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    Job job;

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