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 来启动它们的作业。

At a minimum, launching a batch job requires two things: the Job to be launched and a JobLauncher. Both can be contained within the same context or different contexts. For example, if you launch jobs from the command line, a new JVM is instantiated for each Job. Thus, every job has its own JobLauncher. However, if you run from within a web container that is within the scope of an HttpRequest, there is usually one JobLauncher (configured for asynchronous job launching) that multiple requests invoke to launch their jobs.

Running Jobs from the Command Line

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

If you want to run your jobs from an enterprise scheduler, the command line is the primary interface. This is because most schedulers (with the exception of Quartz, unless using NativeJob) work directly with operating system processes, primarily kicked off with shell scripts. There are many ways to launch a Java process besides a shell script, such as Perl, Ruby, or even build tools, such as Ant or Maven. However, because most people are familiar with shell scripts, this example focuses on them.

The CommandLineJobRunner

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

Because the script launching the job must kick off a Java Virtual Machine, there needs to be a class with a main method to act as the primary entry point. Spring Batch provides an implementation that serves this purpose: CommandLineJobRunner. Note that this is just one way to bootstrap your application. There are many ways to launch a Java process, and this class should in no way be viewed as definitive. The CommandLineJobRunner performs four tasks:

  • Load the appropriate ApplicationContext.

  • Parse command line arguments into JobParameters.

  • Locate the appropriate job based on arguments.

  • Use the JobLauncher provided in the application context to launch the job.

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

All of these tasks are accomplished with only the arguments passed in. The following table describes the required arguments:

Table 1. CommandLineJobRunner arguments

jobPath

The location of the XML file that is used to create an ApplicationContext. This file should contain everything needed to run the complete Job.

jobName

The name of the job to be run.

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

These arguments must be passed in, with the path first and the name second. All arguments after these are considered to be job parameters, are turned into a JobParameters object, and must be in the format of name=value.

Java

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

The following example shows a date passed as a job parameter to a job defined in Java:

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

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

The following example shows a date passed as a job parameter to a job defined in XML:

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

默认情况下,CommandLineJobRunner 使用 DefaultJobParametersConverter,它隐式地将键/值对转换为标识作业参数。然而,你可以明确地指定哪些作业参数是标识,哪些不是,分别通过后缀 truefalse 来表示。

By default, the CommandLineJobRunner uses a DefaultJobParametersConverter that implicitly converts key/value pairs to identifying job parameters. However, you can explicitly specify which job parameters are identifying and which are not by suffixing them with true or false, respectively.

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

In the following example, schedule.date is an identifying job parameter, while vendor.id is not:

<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 覆盖此行为。

You can override this behavior by using a custom 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 对象。

In most cases, you would want to use a manifest to declare your main class in a jar. However, for simplicity, the class was used directly. This example uses the EndOfDay example from the The Domain Language of Batch. The first argument is io.spring.EndOfDayJobConfiguration, which is the fully qualified class name to the configuration class that contains the Job. The second argument, endOfDay, represents the job name. The final argument, schedule.date=2007-05-05,java.time.LocalDate, is converted into a JobParameter object of type java.time.LocalDate.

以下范例显示一个 EndOfDay 配置范例在 Java 中:

The following example shows a sample configuration for endOfDay in 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 对象。

In most cases, you would want to use a manifest to declare your main class in a jar. However, for simplicity, the class was used directly. This example uses the EndOfDay example from the The Domain Language of Batch. The first argument is endOfDayJob.xml, which is the Spring ApplicationContext that contains the Job. The second argument, endOfDay, represents the job name. The final argument, schedule.date=2007-05-05,java.time.LocalDate, is converted into a JobParameter object of type java.time.LocalDate.

以下范例显示一个 EndOfDay 配置范例在 XML 中:

The following example shows a sample configuration for endOfDay in 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

The preceding example is overly simplistic, since there are many more requirements to a run a batch job in Spring Batch in general, but it serves to show the two main requirements of the CommandLineJobRunner: Job and JobLauncher.

Exit Codes

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

When launching a batch job from the command-line, an enterprise scheduler is often used. Most schedulers are fairly dumb and work only at the process level. This means that they only know about some operating system process (such as a shell script that they invoke). In this scenario, the only way to communicate back to the scheduler about the success or failure of a job is through return codes. A return code is a number that is returned to a scheduler by the process to indicate the result of the run. In the simplest case, 0 is success and 1 is failure. However, there may be more complex scenarios, such as “If job A returns 4, kick off job B, and, if it returns 5, kick off job C.” This type of behavior is configured at the scheduler level, but it is important that a processing framework such as Spring Batch provide a way to return a numeric representation of the exit code for a particular batch job. In Spring Batch, this is encapsulated within an ExitStatus, which is covered in more detail in Chapter 5. For the purposes of discussing exit codes, the only important thing to know is that an ExitStatus has an exit code property that is set by the framework (or the developer) and is returned as part of the JobExecution returned from the JobLauncher. The CommandLineJobRunner converts this string value to a number by using the ExitCodeMapper interface:

public interface ExitCodeMapper {

    public int intValue(String exitCode);

}

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

The essential contract of an ExitCodeMapper is that, given a string exit code, a number representation will be returned. The default implementation used by the job runner is the SimpleJvmExitCodeMapper that returns 0 for completion, 1 for generic errors, and 2 for any job runner errors such as not being able to find a Job in the provided context. If anything more complex than the three values above is needed, a custom implementation of the ExitCodeMapper interface must be supplied. Because the CommandLineJobRunner is the class that creates an ApplicationContext and, thus, cannot be 'wired together', any values that need to be overwritten must be autowired. This means that if an implementation of ExitCodeMapper is found within the BeanFactory, it is injected into the runner after the context is created. All that needs to be done to provide your own ExitCodeMapper is to declare the implementation as a root level bean and ensure that it is part of the ApplicationContext that is loaded by the runner.

Running Jobs from within a Web Container

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

Historically, offline processing (such as batch jobs) has been launched from the command-line, as described earlier. However, there are many cases where launching from an HttpRequest is a better option. Many such use cases include reporting, ad-hoc job running, and web application support. Because a batch job (by definition) is long running, the most important concern is to launch the job asynchronously:

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

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

The controller in this case is a Spring MVC controller. See the Spring Framework Reference Guide for more about Spring MVC. The controller launches a Job by using a JobLauncher that has been configured to launch asynchronously, which immediately returns a JobExecution. The Job is likely still running. However, this nonblocking behavior lets the controller return immediately, which is required when handling an HttpRequest. The following listing shows an example:

@Controller
public class JobLauncherController {

    @Autowired
    JobLauncher jobLauncher;

    @Autowired
    Job job;

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