运行作业

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

从命令行运行作业

如果要从企业调度程序运行作业,命令行是主要接口。这是因为大多数调度程序(Quartz 除外,除非使用 `NativeJob`)直接与操作系统进程一起工作,主要通过 shell 脚本启动。除了 shell 脚本之外,还有许多方法可以启动 Java 进程,例如 Perl、Ruby,甚至构建工具,例如 Ant 或 Maven。但是,由于大多数人都熟悉 shell 脚本,因此此示例侧重于 shell 脚本。

CommandLineJobRunner

由于启动作业的脚本必须启动 Java 虚拟机,因此需要一个具有 `main` 方法的类作为主要入口点。Spring Batch 提供了一个实现来满足此目的:`CommandLineJobRunner`。请注意,这只是引导应用程序的一种方法。启动 Java 进程有很多方法,这个类绝不应该被视为最终的解决方案。`CommandLineJobRunner` 执行四个任务:

  • 加载相应的 `ApplicationContext`。

  • 将命令行参数解析为 `JobParameters`。

  • 根据参数找到相应的作业。

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

所有这些任务都只需传递的参数即可完成。下表描述了必需的参数:

表 1. CommandLineJobRunner 参数

jobPath

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

jobName

要运行的作业的名称。

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

  • Java

  • XML

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

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

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

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

默认情况下,`CommandLineJobRunner` 使用 `DefaultJobParametersConverter`,它隐式地将键值对转换为标识作业参数。但是,可以通过分别在其后附加 `true` 或 `false` 来显式指定哪些作业参数是标识参数,哪些不是。

在以下示例中,`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

  • XML

在大多数情况下,您可能希望使用清单在 jar 中声明您的 `main` 类。但是,为简单起见,直接使用了该类。此示例使用 批处理的领域语言 中的 `EndOfDay` 示例。第一个参数是 `io.spring.EndOfDayJobConfiguration`,它是包含 Job 的配置类的完全限定类名。第二个参数 `endOfDay` 表示作业名称。最后一个参数 `schedule.date=2007-05-05,java.time.LocalDate` 被转换为类型为 `java.time.LocalDate` 的 `JobParameter` 对象。

以下示例显示了 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();
    }
}

在大多数情况下,您应该使用清单文件在 jar 中声明您的main类。但是,为简便起见,此处直接使用了该类。此示例使用来自批处理的领域语言EndOfDay示例。第一个参数是endOfDayJob.xml,它是包含Job的Spring ApplicationContext。第二个参数endOfDay表示作业名称。最后一个参数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

退出代码

从命令行启动批处理作业时,通常会使用企业调度程序。大多数调度程序都相当简单,只在进程级别工作。这意味着它们只知道某些操作系统进程(例如它们调用的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的一部分。

在Web容器中运行作业

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

Async Job Launcher Sequence from web container
图1. 来自Web容器的异步作业启动器序列

在这种情况下,控制器是Spring MVC控制器。有关Spring MVC的更多信息,请参阅Spring框架参考指南。控制器使用已配置为异步启动作业的JobLauncher启动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());
    }
}