运行作业

最起码,启动一个批处理作业需要两样东西:待启动的 Job 和一个 JobLauncher。它们可以包含在同一个上下文或不同的上下文中。例如,如果你从命令行启动作业,则会为每个 Job 实例化一个新的 JVM。因此,每个作业都有自己的 JobLauncher。然而,如果你从一个 Web 容器内部运行,且该容器在 HttpRequest 的作用域内,通常会有一个 JobLauncher(配置为异步作业启动)供多个请求调用来启动它们的作业。

从命令行运行作业

如果你想从企业级调度器运行作业,命令行是主要接口。这是因为大多数调度器(除了 Quartz,除非使用 NativeJob)直接与操作系统进程一起工作,主要通过 shell 脚本启动。除了 shell 脚本之外,还有许多启动 Java 进程的方法,例如 Perl、Ruby,甚至构建工具,例如 Ant 或 Maven。然而,由于大多数人熟悉 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,它隐式地将键/值对转换为标识性作业参数。但是,你可以通过分别在其后加上 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

  • XML

在大多数情况下,你会希望使用清单文件在 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();
    }
}

在大多数情况下,你会希望使用清单文件在 jar 包中声明你的 main 类。然而,为了简单起见,此处直接使用了该类。本例使用了 批处理的领域语言 中的 EndOfDay 示例。第一个参数是 endOfDayJob.xml,它是包含 Job 的 Spring ApplicationContext。第二个参数 endOfDay, 代表作业名称。最后一个参数 schedule.date=2007-05-05,java.time.LocalDate 被转换为类型为 java.time.LocalDateJobParameter 对象。

以下示例展示了 XML 中 endOfDay 的示例配置

<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;如果作业 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 Framework 参考指南。控制器使用配置为异步启动的 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());
    }
}