# 配置和运行作业

## 配置和运行作业

XMLJavaBoth

在[领域部分](domain.html#domainLanguageOfBatch)中，使用以下图表作为指导，讨论了总体架构设计：

![图 2.1:批处理原型](https://docs.spring.io/spring-batch/docs/current/reference/html/images/spring-batch-reference-model.png)

图 1。批处理模式

虽然`Job`对象看起来像是一个用于步骤的简单容器，但开发人员必须了解许多配置选项。此外，对于如何运行`Job`以及在运行期间如何存储其元数据，有许多考虑因素。本章将解释`Job`的各种配置选项和运行时关注点。

### 配置作业

[`Job`]（#configurejob）接口有多个实现方式。然而，构建者会抽象出配置上的差异。

```
@Bean
public Job footballJob() {
    return this.jobBuilderFactory.get("footballJob")
                     .start(playerLoad())
                     .next(gameLoad())
                     .next(playerSummarization())
                     .build();
}
```

一个`Job`（以及其中的任何`Step`）需要一个`JobRepository`。`JobRepository`的配置是通过[`BatchConfigurer`]来处理的。

上面的示例演示了由三个`Step`实例组成的`Job`实例。与作业相关的构建器还可以包含有助于并行（`Split`）、声明性流控制（`Decision`）和流定义外部化（`Flow`）的其他元素。

无论你使用 Java 还是 XML，[`Job`]（#configurejob）接口都有多个实现。然而，名称空间抽象出了配置上的差异。它只有三个必需的依赖项：一个名称，`JobRepository`，和一个`Step`实例的列表。

```
<job id="footballJob">
    <step id="playerload"          parent="s1" next="gameLoad"/>
    <step id="gameLoad"            parent="s2" next="playerSummarization"/>
    <step id="playerSummarization" parent="s3"/>
</job>
```

这里的示例使用父 Bean 定义来创建步骤。有关内联声明特定步骤详细信息的更多选项，请参见[阶跃配置](step.html#configureStep)一节。XML 名称空间默认情况下引用一个 ID 为“JobRepository”的存储库，这是一个合理的默认值。但是，这可以显式地重写：

```
<job id="footballJob" job-repository="specialRepository">
    <step id="playerload"          parent="s1" next="gameLoad"/>
    <step id="gameLoad"            parent="s3" next="playerSummarization"/>
    <step id="playerSummarization" parent="s3"/>
</job>
```

除了步骤之外，作业配置还可以包含有助于并行（`<split>`）、声明性流控制（`<decision>`）和流定义外部化（`<flow/>`）的其他元素。

#### 可重启性

执行批处理作业时的一个关键问题与`Job`重新启动时的行为有关。如果对于特定的`Job`已经存在`JobExecution`，则将`Job`的启动视为“重新启动”。理想情况下，所有的工作都应该能够在它们停止的地方启动，但是在某些情况下这是不可能的。* 完全由开发人员来确保在此场景中创建一个新的`JobInstance`。* 但是， Spring 批处理确实提供了一些帮助。如果`Job`永远不应该重新启动，而应该始终作为新的`JobInstance`的一部分运行，那么可重启属性可以设置为“false”。

下面的示例展示了如何在 XML 中将`restartable`字段设置为`false`:

XML 配置

```
<job id="footballJob" restartable="false">
    ...
</job>
```

下面的示例展示了如何在 Java 中将`restartable`字段设置为`false`:

Java 配置

```
@Bean
public Job footballJob() {
    return this.jobBuilderFactory.get("footballJob")
                     .preventRestart()
                     ...
                     .build();
}
```

换一种说法，将 Restartable 设置为 false 意味着“this`Job`不支持重新启动”。重新启动不可重启的`Job`会导致抛出`JobRestartException`。

```
Job job = new SimpleJob();
job.setRestartable(false);

JobParameters jobParameters = new JobParameters();

JobExecution firstExecution = jobRepository.createJobExecution(job, jobParameters);
jobRepository.saveOrUpdate(firstExecution);

try {
    jobRepository.createJobExecution(job, jobParameters);
    fail();
}
catch (JobRestartException e) {
    // expected
}
```

这段 JUnit 代码展示了如何在第一次为不可重启作业创建`JobExecution`时尝试创建`JobExecution`不会导致任何问题。但是，第二次尝试将抛出`JobRestartException`。

#### 拦截作业执行

在作业的执行过程中，通知其生命周期中的各种事件可能是有用的，以便可以执行自定义代码。通过在适当的时间调用`JobListener`，`SimpleJob`允许这样做：

```
public interface JobExecutionListener {

    void beforeJob(JobExecution jobExecution);

    void afterJob(JobExecution jobExecution);

}
```

`JobListeners`可以通过在作业上设置侦听器来添加到`SimpleJob`。

下面的示例展示了如何将 Listener 元素添加到 XML 作业定义中：

XML 配置

```
<job id="footballJob">
    <step id="playerload"          parent="s1" next="gameLoad"/>
    <step id="gameLoad"            parent="s2" next="playerSummarization"/>
    <step id="playerSummarization" parent="s3"/>
    <listeners>
        <listener ref="sampleListener"/>
    </listeners>
</job>
```

下面的示例展示了如何将侦听器方法添加到 Java 作业定义中：

Java 配置

```
@Bean
public Job footballJob() {
    return this.jobBuilderFactory.get("footballJob")
                     .listener(sampleListener())
                     ...
                     .build();
}
```

应该注意的是，无论`afterJob`方法的成功或失败，都调用`Job`方法。如果需要确定成功或失败，则可以从`JobExecution`中获得如下：

```
public void afterJob(JobExecution jobExecution){
    if (jobExecution.getStatus() == BatchStatus.COMPLETED ) {
        //job success
    }
    else if (jobExecution.getStatus() == BatchStatus.FAILED) {
        //job failure
    }
}
```

与此接口对应的注释是：

* `@BeforeJob`

* `@AfterJob`

#### 继承父作业

如果一组作业共享相似但不相同的配置，那么定义一个“父”`Job`可能会有所帮助，具体的作业可以从该“父”中继承属性。与 Java 中的类继承类似，“child”`Job`将把它的元素和属性与父元素和属性结合在一起。

在下面的示例中，“basejob”是一个抽象的`Job`定义，它只定义了一个侦听器列表。`Job`“job1”是一个具体的定义，它继承了“Basejob”的侦听器列表，并将其与自己的侦听器列表合并，以生成一个`Job`，其中包含两个侦听器和一个`Step`，即“步骤 1”。

```
<job id="baseJob" abstract="true">
    <listeners>
        <listener ref="listenerOne"/>
    <listeners>
</job>

<job id="job1" parent="baseJob">
    <step id="step1" parent="standaloneStep"/>

    <listeners merge="true">
        <listener ref="listenerTwo"/>
    <listeners>
</job>
```

有关更多详细信息，请参见[从父步骤继承](step.html#inheritingFromParentStep)一节。

#### JobParametersValidator

在 XML 命名空间中声明的作业或使用`AbstractJob`的任意子类可以在运行时为作业参数声明验证器。例如，当你需要断言一个作业是以其所有的强制参数启动时，这是有用的。有一个`DefaultJobParametersValidator`可以用来约束简单的强制参数和可选参数的组合，对于更复杂的约束，你可以自己实现接口。

验证程序的配置通过 XML 命名空间通过作业的一个子元素得到支持，如下面的示例所示：

```
<job id="job1" parent="baseJob3">
    <step id="step1" parent="standaloneStep"/>
    <validator ref="parametersValidator"/>
</job>
```

验证器可以指定为引用（如前面所示），也可以指定为 bean 名称空间中的嵌套 Bean 定义。

通过 Java Builders 支持验证器的配置，如以下示例所示：

```
@Bean
public Job job1() {
    return this.jobBuilderFactory.get("job1")
                     .validator(parametersValidator())
                     ...
                     .build();
}
```

### Java 配置

Spring 3 带来了通过 Java 而不是 XML 配置应用程序的能力。从 Spring Batch2.2.0 开始，可以使用相同的 Java 配置配置来配置批处理作业。基于 Java 的配置有两个组件：`@EnableBatchProcessing`注释和两个构建器。

`@EnableBatchProcessing`的工作原理与 Spring 家族中的其他 @enable\* 注释类似。在这种情况下，`@EnableBatchProcessing`提供了用于构建批处理作业的基本配置。在这个基本配置中，除了许多可用于自动连线的 bean 之外，还创建了`StepScope`实例：

* `JobRepository`: Bean 名称“jobrepository”

* `JobLauncher`: Bean 名称“joblauncher”

* `JobRegistry`: Bean 名称“jobregistry”

* `PlatformTransactionManager`: Bean 名称“TransactionManager”

* `JobBuilderFactory`: Bean name“jobbuilders”

* `StepBuilderFactory`: Bean 名称“StepBuilders”

此配置的核心接口是`BatchConfigurer`。默认的实现提供了上面提到的 bean，并且需要在要提供的上下文中提供一个`DataSource`作为 Bean。这个数据源由 JobRepository 使用。你可以通过创建`BatchConfigurer`接口的自定义实现来定制这些 bean 中的任何一个。通常，扩展`DefaultBatchConfigurer`（如果没有找到`BatchConfigurer`，则提供该扩展）并重写所需的吸气器就足够了。然而，可能需要从头开始实现自己的功能。下面的示例展示了如何提供自定义事务管理器：

```
@Bean
public BatchConfigurer batchConfigurer(DataSource dataSource) {
	return new DefaultBatchConfigurer(dataSource) {
		@Override
		public PlatformTransactionManager getTransactionManager() {
			return new MyTransactionManager();
		}
	};
}
```

|   |只有一个配置类需要`@EnableBatchProcessing`注释。一旦<br/>对一个类进行了注释，就可以使用上面的所有内容了。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

有了基本配置，用户就可以使用提供的生成器工厂来配置作业。下面的示例显示了配置了`JobBuilderFactory`和`StepBuilderFactory`的两步作业：

```
@Configuration
@EnableBatchProcessing
@Import(DataSourceConfiguration.class)
public class AppConfig {

    @Autowired
    private JobBuilderFactory jobs;

    @Autowired
    private StepBuilderFactory steps;

    @Bean
    public Job job(@Qualifier("step1") Step step1, @Qualifier("step2") Step step2) {
        return jobs.get("myJob").start(step1).next(step2).build();
    }

    @Bean
    protected Step step1(ItemReader<Person> reader,
                         ItemProcessor<Person, Person> processor,
                         ItemWriter<Person> writer) {
        return steps.get("step1")
            .<Person, Person> chunk(10)
            .reader(reader)
            .processor(processor)
            .writer(writer)
            .build();
    }

    @Bean
    protected Step step2(Tasklet tasklet) {
        return steps.get("step2")
            .tasklet(tasklet)
            .build();
    }
}
```

### 配置 JobRepository

当使用`@EnableBatchProcessing`时，将为你提供一个`JobRepository`。本节讨论如何配置自己的配置。

如前面所述，[`JobRepository`]（#configurejob）用于 Spring 批处理中各种持久化域对象的基本增删改查操作，例如`JobExecution`和`StepExecution`。它是许多主要框架特性所要求的，例如`JobLauncher`、`Job`和`Step`。

批处理名称空间抽象出了`JobRepository`实现及其协作者的许多实现细节。然而，仍然有一些可用的配置选项，如以下示例所示：

XML 配置

```
<job-repository id="jobRepository"
    data-source="dataSource"
    transaction-manager="transactionManager"
    isolation-level-for-create="SERIALIZABLE"
    table-prefix="BATCH_"
	max-varchar-length="1000"/>
```

除了`id`之外，上面列出的配置选项都不是必需的。如果没有设置，将使用上面显示的默认值。以上所示是为了提高认识。`max-varchar-length`默认为 2500，这是[示例模式脚本](schema-appendix.html#metaDataSchemaOverview)中的长`VARCHAR`列的长度。

当使用 Java 配置时，将为你提供`JobRepository`。如果提供了`DataSource`，则提供了基于 JDBC 的一个，如果没有，则提供基于`Map`的一个。但是，你可以通过`BatchConfigurer`接口的实现来定制`JobRepository`的配置。

Java 配置

```
...
// This would reside in your BatchConfigurer implementation
@Override
protected JobRepository createJobRepository() throws Exception {
    JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
    factory.setDataSource(dataSource);
    factory.setTransactionManager(transactionManager);
    factory.setIsolationLevelForCreate("ISOLATION_SERIALIZABLE");
    factory.setTablePrefix("BATCH_");
    factory.setMaxVarCharLength(1000);
    return factory.getObject();
}
...
```

除了数据源和 TransactionManager 之外，上面列出的配置选项都不是必需的。如果没有设置，将使用上面显示的默认值。以上所示是为了提高认识。最大 VARCHAR 长度默认为 2500，这是`VARCHAR`中的长[示例模式脚本](schema-appendix.html#metaDataSchemaOverview)列的长度

#### JobRepository 的事务配置

如果使用了名称空间或提供的`FactoryBean`，则会在存储库周围自动创建事务建议。这是为了确保批处理元数据（包括在发生故障后重新启动所必需的状态）被正确地持久化。如果存储库方法不是事务性的，那么框架的行为就没有得到很好的定义。`create*`方法属性中的隔离级别是单独指定的，以确保在启动作业时，如果两个进程试图同时启动相同的作业，则只有一个进程成功。该方法的默认隔离级别是`SERIALIZABLE`，这是非常激进的。`READ_COMMITTED`同样有效。如果两个过程不太可能以这种方式碰撞，`READ_UNCOMMITTED`就可以了。然而，由于对`create*`方法的调用相当短，所以只要数据库平台支持它，`SERIALIZED`不太可能导致问题。然而，这一点可以被重写。

下面的示例展示了如何覆盖 XML 中的隔离级别：

XML 配置

```
<job-repository id="jobRepository"
                isolation-level-for-create="REPEATABLE_READ" />
```

下面的示例展示了如何在 Java 中重写隔离级别：

Java 配置

```
// This would reside in your BatchConfigurer implementation
@Override
protected JobRepository createJobRepository() throws Exception {
    JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
    factory.setDataSource(dataSource);
    factory.setTransactionManager(transactionManager);
    factory.setIsolationLevelForCreate("ISOLATION_REPEATABLE_READ");
    return factory.getObject();
}
```

如果没有使用名称空间或工厂 bean，那么使用 AOP 配置存储库的事务行为也是必不可少的。

下面的示例展示了如何在 XML 中配置存储库的事务行为：

XML 配置

```
<aop:config>
    <aop:advisor
           pointcut="execution(* org.springframework.batch.core..*Repository+.*(..))"/>
    <advice-ref="txAdvice" />
</aop:config>

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*" />
    </tx:attributes>
</tx:advice>
```

前面的片段可以按原样使用，几乎没有变化。还请记住包括适当的名称空间声明，并确保 Spring-tx 和 Spring- AOP（或整个 Spring）都在 Classpath 上。

下面的示例展示了如何在 Java 中配置存储库的事务行为：

Java 配置

```
@Bean
public TransactionProxyFactoryBean baseProxy() {
	TransactionProxyFactoryBean transactionProxyFactoryBean = new TransactionProxyFactoryBean();
	Properties transactionAttributes = new Properties();
	transactionAttributes.setProperty("*", "PROPAGATION_REQUIRED");
	transactionProxyFactoryBean.setTransactionAttributes(transactionAttributes);
	transactionProxyFactoryBean.setTarget(jobRepository());
	transactionProxyFactoryBean.setTransactionManager(transactionManager());
	return transactionProxyFactoryBean;
}
```

#### 更改表格前缀

`JobRepository`的另一个可修改的属性是元数据表的表前缀。默认情况下，它们都以`BATCH_`开头。`BATCH_JOB_EXECUTION`和`BATCH_STEP_EXECUTION`是两个例子。然而，有潜在的理由修改这个前缀。如果需要将模式名称前置到表名，或者如果同一模式中需要多个元数据表集合，则需要更改表前缀：

下面的示例展示了如何更改 XML 中的表前缀：

XML 配置

```
<job-repository id="jobRepository"
                table-prefix="SYSTEM.TEST_" />
```

下面的示例展示了如何在 Java 中更改表前缀：

Java 配置

```
// This would reside in your BatchConfigurer implementation
@Override
protected JobRepository createJobRepository() throws Exception {
    JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
    factory.setDataSource(dataSource);
    factory.setTransactionManager(transactionManager);
    factory.setTablePrefix("SYSTEM.TEST_");
    return factory.getObject();
}
```

给定上述更改，对元数据表的每个查询都以`SYSTEM.TEST_`作为前缀。`BATCH_JOB_EXECUTION`被称为系统。`TEST_JOB_EXECUTION`。

|   |只有表前缀是可配置的。表和列名不是。|
|---|--------------------------------------------------------------------------|

#### 内存存储库

在某些情况下，你可能不希望将域对象持久化到数据库。原因之一可能是速度；在每个提交点存储域对象需要额外的时间。另一个原因可能是，你不需要为一份特定的工作坚持现状。出于这个原因， Spring 批处理提供了作业存储库的内存`Map`版本。

下面的示例显示了`MapJobRepositoryFactoryBean`在 XML 中的包含：

XML 配置

```
<bean id="jobRepository"
  class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean">
    <property name="transactionManager" ref="transactionManager"/>
</bean>
```

下面的示例显示了在 Java 中包含`MapJobRepositoryFactoryBean`:

Java 配置

```
// This would reside in your BatchConfigurer implementation
@Override
protected JobRepository createJobRepository() throws Exception {
    MapJobRepositoryFactoryBean factory = new MapJobRepositoryFactoryBean();
    factory.setTransactionManager(transactionManager);
    return factory.getObject();
}
```

请注意，内存中的存储库是不稳定的，因此不允许在 JVM 实例之间重新启动。它也不能保证具有相同参数的两个作业实例同时启动，并且不适合在多线程作业或本地分区`Step`中使用。因此，只要你需要这些特性，就可以使用存储库的数据库版本。

但是，它确实需要定义事务管理器，因为存储库中存在回滚语义，并且业务逻辑可能仍然是事务性的（例如 RDBMS 访问）。对于测试目的，许多人发现`ResourcelessTransactionManager`很有用。

|   |在 V4 中，`MapJobRepositoryFactoryBean`和相关的类已被弃用，并计划在 V5 中删除<br/>。如果希望使用内存中的作业存储库，可以使用嵌入式数据库<br/>，比如 H2、 Apache Derby 或 HSQLDB。有几种方法可以创建嵌入式数据库并在<br/>你的 Spring 批处理应用程序中使用它。一种方法是使用[Spring JDBC](https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#jdbc-embedded-database-support)中的 API:<br/><br/>```<br/>@Bean<br/>public DataSource dataSource() {<br/>    return new EmbeddedDatabaseBuilder()<br/>            .setType(EmbeddedDatabaseType.H2)<br/>            .addScript("/org/springframework/batch/core/schema-drop-h2.sql")<br/>            .addScript("/org/springframework/batch/core/schema-h2.sql")<br/>            .build();<br/>}<br/>```<br/><br/>一旦你在应用程序上下文中将嵌入式数据源定义为 Bean，如果你使用`@EnableBatchProcessing`，就应该自动选择<br/>。否则，你可以使用<br/>基于`JobRepositoryFactoryBean`的 JDBC 手动配置它，如[配置 JobRepository 部分](#configuringJobRepository)所示。|
|---| |

#### 存储库中的非标准数据库类型

如果你使用的数据库平台不在受支持的平台列表中，那么如果 SQL 变量足够接近，则可以使用受支持的类型之一。要做到这一点，你可以使用 RAW`JobRepositoryFactoryBean`而不是名称空间快捷方式，并使用它将数据库类型设置为最接近的匹配。

下面的示例展示了如何使用`JobRepositoryFactoryBean`将数据库类型设置为 XML 中最接近的匹配：

XML 配置

```
<bean id="jobRepository" class="org...JobRepositoryFactoryBean">
    <property name="databaseType" value="db2"/>
    <property name="dataSource" ref="dataSource"/>
</bean>
```

下面的示例展示了如何使用`JobRepositoryFactoryBean`将数据库类型设置为 Java 中最接近的匹配：

Java 配置

```
// This would reside in your BatchConfigurer implementation
@Override
protected JobRepository createJobRepository() throws Exception {
    JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
    factory.setDataSource(dataSource);
    factory.setDatabaseType("db2");
    factory.setTransactionManager(transactionManager);
    return factory.getObject();
}
```

（如果没有指定，`JobRepositoryFactoryBean`会尝试从`DataSource`中自动检测数据库类型，）平台之间的主要差异主要是由主键递增策略造成的，因此，通常可能还需要覆盖`incrementerFactory`（使用 Spring 框架中的一个标准实现）。

如果连这都不起作用，或者你没有使用 RDBMS，那么唯一的选择可能是实现`Dao`所依赖的各种`SimpleJobRepository`接口，并以正常的方式手动连接。

### 配置一个 joblauncher

当使用`@EnableBatchProcessing`时，将为你提供一个`JobRegistry`。本节讨论如何配置自己的配置。

`JobLauncher`接口的最基本实现是`SimpleJobLauncher`。它唯一需要的依赖关系是`JobRepository`，以便获得执行。

下面的示例显示了 XML 中的`SimpleJobLauncher`:

XML 配置

```
<bean id="jobLauncher"
      class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
    <property name="jobRepository" ref="jobRepository" />
</bean>
```

下面的示例显示了 Java 中的`SimpleJobLauncher`:

Java 配置

```
...
// This would reside in your BatchConfigurer implementation
@Override
protected JobLauncher createJobLauncher() throws Exception {
	SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
	jobLauncher.setJobRepository(jobRepository);
	jobLauncher.afterPropertiesSet();
	return jobLauncher;
}
...
```

一旦获得了[工作执行](domain.html#domainLanguageOfBatch)，它就被传递给`Job`的执行方法，最终将`JobExecution`返回给调用者，如下图所示：

![作业启动器序列](https://docs.spring.io/spring-batch/docs/current/reference/html/images/job-launcher-sequence-sync.png)

图 2。作业启动器序列

这个序列很简单，从调度程序启动时效果很好。然而，在尝试从 HTTP 请求启动时会出现问题。在这种情况下，启动需要异步完成，以便`SimpleJobLauncher`立即返回其调用方。这是因为，在长时间运行的进程（如批处理）所需的时间内保持 HTTP 请求的开放状态是不好的做法。下图显示了一个示例序列：

![异步作业启动器序列](https://docs.spring.io/spring-batch/docs/current/reference/html/images/job-launcher-sequence-async.png)

图 3。异步作业启动器序列

可以通过配置`TaskExecutor`将`SimpleJobLauncher`配置为允许这种情况。

下面的 XML 示例显示了配置为立即返回的`SimpleJobLauncher`:

XML 配置

```
<bean id="jobLauncher"
      class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
    <property name="jobRepository" ref="jobRepository" />
    <property name="taskExecutor">
        <bean class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
    </property>
</bean>
```

下面的 Java 示例显示了配置为立即返回的`SimpleJobLauncher`:

Java 配置

```
@Bean
public JobLauncher jobLauncher() {
	SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
	jobLauncher.setJobRepository(jobRepository());
	jobLauncher.setTaskExecutor(new SimpleAsyncTaskExecutor());
	jobLauncher.afterPropertiesSet();
	return jobLauncher;
}
```

Spring `TaskExecutor`接口的任何实现都可以用来控制如何异步执行作业。

### 运行作业

至少，启动批处理作业需要两个条件：启动`Job`和`JobLauncher`。两者都可以包含在相同的上下文中，也可以包含在不同的上下文中。例如，如果从命令行启动一个作业，将为每个作业实例化一个新的 JVM，因此每个作业都有自己的`JobLauncher`。但是，如果在`HttpRequest`范围内的 Web 容器中运行，通常会有一个`JobLauncher`，该配置用于异步作业启动，多个请求将调用以启动其作业。

#### 从命令行运行作业

对于希望从 Enterprise 调度器运行作业的用户，命令行是主要的接口。这是因为大多数调度程序（Quartz 除外，除非使用 nativeJob）直接与操作系统进程一起工作，主要是通过 shell 脚本开始的。除了 shell 脚本之外，还有许多启动 Java 进程的方法，例如 Perl、Ruby，甚至是 Ant 或 Maven 之类的“构建工具”。但是，由于大多数人都熟悉 shell 脚本，因此本例将重点讨论它们。

##### The CommandlineJobrunner

因为启动作业的脚本必须启动一个 Java 虚拟机，所以需要有一个具有 main 方法的类来充当主要入口点。 Spring 批处理提供了一种实现，它仅服务于此目的：`CommandLineJobRunner`。需要注意的是，这只是引导应用程序的一种方法，但是启动 Java 进程的方法有很多，并且这个类绝不应该被视为确定的。`CommandLineJobRunner`执行四项任务：

* 装入适当的`ApplicationContext`

* 将命令行参数解析为`JobParameters`

* 根据参数定位适当的作业

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

所有这些任务都是仅使用传入的参数来完成的。以下是必要的论据：

|jobPath|将用于<br/>的 XML 文件的位置创建一个`ApplicationContext`。此文件<br/>应该包含运行完整<br/>作业所需的所有内容|
|-------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
|jobName|要运行的作业的名称。|

这些参数必须首先传递路径，然后传递名称。在这些参数之后的所有参数都被认为是作业参数，被转换为一个 JobParameters 对象，并且必须是“name=value”的格式。

下面的示例显示了作为作业参数传递给 XML 中未定义的作业的日期：

```
<bash$ java CommandLineJobRunner endOfDayJob.xml endOfDay schedule.date(date)=2007/05/05
```

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

```
<bash$ java CommandLineJobRunner io.spring.EndOfDayJobConfiguration endOfDay schedule.date(date)=2007/05/05
```

|   |默认情况下，`CommandLineJobRunner`使用`DefaultJobParametersConverter`，它隐式地将<br/>键/值对转换为标识作业参数。但是，在下面的示例中，可以通过分别使用`+`或`-`前缀来显式地指定<br/>哪些作业参数是标识的，哪些不是标识的。<br/><br/>，`schedule.date`是一个标识作业参数，而`vendor.id`不是：<br/><br/>```<br/><bash$ java CommandLineJobRunner endOfDayJob.xml endOfDay \<br/>                                 +schedule.date(date)=2007/05/05 -vendor.id=123<br/>```<br/>```<br/><bash$ java CommandLineJobRunner io.spring.EndOfDayJobConfiguration endOfDay \<br/>                                 +schedule.date(date)=2007/05/05 -vendor.id=123<br/>```<br/>可以通过使用自定义`JobParametersConverter`来重写此行为。|
|---| |

在大多数情况下，你可能希望使用清单在 JAR 中声明主类，但为了简单起见，直接使用了该类。这个示例使用的是来自[DomainLanguageofBatch](domain.html#domainLanguageOfBatch)的相同的“Endofday”示例。第一个参数是“endofdayjob.xml”，这是 Spring 包含`Job`的应用上下文。第二个参数“Endofday”表示工作名称。最后一个参数“schedule.date=2007/05/05”被转换为一个 JobParameters 对象。

下面的示例显示了在 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.SimpleJobLauncher" />
```

在大多数情况下，你希望使用清单在 JAR 中声明主类，但为了简单起见，直接使用了该类。这个示例使用的是来自[DomainLanguageofBatch](domain.html#domainLanguageOfBatch)的相同的“Endofday”示例。第一个参数是“IO. Spring.EndofdayJobConfiguration”，它是包含该作业的配置类的完全限定类名称。第二个参数“Endofday”表示工作名称。最后一个参数’schedule.date=2007/05/05’被转换为`JobParameters`对象。下面是 Java 配置的一个示例：

下面的示例显示了在 Java 中`endOfDay`的示例配置：

```
@Configuration
@EnableBatchProcessing
public class EndOfDayJobConfiguration {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Bean
    public Job endOfDay() {
        return this.jobBuilderFactory.get("endOfDay")
    				.start(step1())
    				.build();
    }

    @Bean
    public Step step1() {
        return this.stepBuilderFactory.get("step1")
    				.tasklet((contribution, chunkContext) -> null)
    				.build();
    }
}
```

前面的示例过于简单，因为在 Spring 批处理中运行一个批处理作业通常有更多的需求，但是它用于显示`CommandLineJobRunner`的两个主要需求：`Job`和`JobLauncher`。

##### exitcodes

当从命令行启动批处理作业时，通常使用 Enterprise 调度器。大多数调度器都相当笨拙，只能在流程级别工作。这意味着他们只知道一些操作系统进程，比如他们正在调用的 shell 脚本。在这种情况下，将工作的成功或失败反馈给调度程序的唯一方法是通过返回代码。返回代码是进程返回给调度程序的一个数字，它指示运行的结果。在最简单的情况下：0 是成功，1 是失败。然而，可能有更复杂的情况：如果作业 A 返回 4，则启动作业 B，如果它返回 5，则启动作业 C。这种类型的行为是在计划程序级别上配置的，但是重要的是， Spring 批处理框架提供了一种方法来返回用于特定批处理作业的“退出代码”的数字表示。在 Spring 批处理中，这被封装在`ExitStatus`中，这在第 5 章中有更详细的介绍。为了讨论退出代码，唯一需要知道的是`ExitStatus`具有一个退出代码属性，该属性由框架（或开发人员）设置，并作为从`JobLauncher`返回的`JobExecution`的一部分返回。`CommandLineJobRunner`使用`ExitCodeMapper`接口将这个字符串值转换为一个数字：

```
public interface ExitCodeMapper {

    public int intValue(String exitCode);

}
```

`ExitCodeMapper`的基本契约是，给定一个字符串退出代码，将返回一个数字表示。Job Runner 使用的默认实现是`SimpleJvmExitCodeMapper`，它返回 0 表示完成，1 表示泛型错误，2 表示任何 Job Runner 错误，例如无法在提供的上下文中找到`Job`。如果需要比上述 3 个值更复杂的值，则必须提供`ExitCodeMapper`接口的自定义实现。因为`CommandLineJobRunner`是创建`ApplicationContext`的类，因此不能“连线在一起”，所以需要重写的任何值都必须是自动连线的。这意味着，如果在`BeanFactory`中找到了`ExitCodeMapper`的实现，则将在创建上下文后将其注入到运行器中。要提供你自己的`ExitCodeMapper`，需要做的就是将实现声明为根级别 Bean，并确保它是由运行器加载的`ApplicationContext`的一部分。

#### 在 Web 容器中运行作业

从历史上看，离线处理（如批处理作业）是从命令行启动的，如上文所述。然而，在许多情况下，从`HttpRequest`发射是更好的选择。许多这样的用例包括报告、临时作业运行和 Web 应用程序支持。因为按定义，批处理作业是长时间运行的，所以最重要的问题是确保异步启动该作业：

![基于 Web 容器的异步作业启动器序列](https://docs.spring.io/spring-batch/docs/current/reference/html/images/launch-from-request.png)

图 4。来自 Web 容器的异步作业启动器序列

在这种情况下，控制器是 Spring MVC 控制器。关于 Spring MVC 的更多信息可以在这里找到：的`Job`启动`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());
    }
}
```

### 高级元数据使用

到目前为止，`JobLauncher`和`JobRepository`接口都已经讨论过了。它们一起表示作业的简单启动，以及批处理域对象的基本操作：

![作业存储库](https://docs.spring.io/spring-batch/docs/current/reference/html/images/job-repository.png)

图 5。作业存储库

a`JobLauncher`使用`JobRepository`来创建新的`JobExecution`对象并运行它们。`Job`和`Step`实现稍后将使用相同的`JobRepository`用于运行作业期间相同执行的基本更新。对于简单的场景，基本的操作就足够了，但是在具有数百个批处理任务和复杂的调度需求的大批处理环境中，需要对元数据进行更高级的访问：

![作业存储库高级版](https://docs.spring.io/spring-batch/docs/current/reference/html/images/job-repository-advanced.png)

图 6。高级作业存储库访问

下面将讨论`JobExplorer`和`JobOperator`接口，它们添加了用于查询和控制元数据的附加功能。

#### 查询存储库

在任何高级特性之前，最基本的需求是查询存储库中现有执行的能力。此功能由`JobExplorer`接口提供：

```
public interface JobExplorer {

    List<JobInstance> getJobInstances(String jobName, int start, int count);

    JobExecution getJobExecution(Long executionId);

    StepExecution getStepExecution(Long jobExecutionId, Long stepExecutionId);

    JobInstance getJobInstance(Long instanceId);

    List<JobExecution> getJobExecutions(JobInstance jobInstance);

    Set<JobExecution> findRunningJobExecutions(String jobName);
}
```

从上面的方法签名中可以明显看出，`JobExplorer`是`JobRepository`的只读版本，并且，像`JobRepository`一样，可以通过使用工厂 Bean 轻松地对其进行配置：

下面的示例展示了如何在 XML 中配置`JobExplorer`:

XML 配置

```
<bean id="jobExplorer" class="org.spr...JobExplorerFactoryBean"
      p:dataSource-ref="dataSource" />
```

下面的示例展示了如何在 Java 中配置`JobExplorer`:

Java 配置

```
...
// This would reside in your BatchConfigurer implementation
@Override
public JobExplorer getJobExplorer() throws Exception {
	JobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean();
	factoryBean.setDataSource(this.dataSource);
	return factoryBean.getObject();
}
...
```

[在本章的前面](#repositoryTablePrefix)，我们注意到`JobRepository`的表前缀可以进行修改以允许不同的版本或模式。因为`JobExplorer`与相同的表一起工作，所以它也需要设置前缀的能力。

下面的示例展示了如何在 XML 中设置`JobExplorer`的表前缀：

XML 配置

```
<bean id="jobExplorer" class="org.spr...JobExplorerFactoryBean"
		p:tablePrefix="SYSTEM."/>
```

下面的示例展示了如何在 Java 中设置`JobExplorer`的表前缀：

Java 配置

```
...
// This would reside in your BatchConfigurer implementation
@Override
public JobExplorer getJobExplorer() throws Exception {
	JobExplorerFactoryBean factoryBean = new JobExplorerFactoryBean();
	factoryBean.setDataSource(this.dataSource);
	factoryBean.setTablePrefix("SYSTEM.");
	return factoryBean.getObject();
}
...
```

#### JobRegistry

a`JobRegistry`（及其父接口`JobLocator`）不是强制性的，但如果你想跟踪上下文中哪些作业可用，它可能会很有用。当工作在其他地方创建时（例如，在子上下文中），它对于在应用程序上下文中集中收集工作也很有用。还可以使用自定义`JobRegistry`实现来操作已注册作业的名称和其他属性。该框架只提供了一个实现，它基于从作业名称到作业实例的简单映射。

下面的示例展示了如何为 XML 中定义的作业包含`JobRegistry`:

```
<bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry" />
```

下面的示例展示了如何为 Java 中定义的作业包含`JobRegistry`:

当使用`@EnableBatchProcessing`时，将为你提供一个`JobRegistry`。如果你想配置自己的：

```
...
// This is already provided via the @EnableBatchProcessing but can be customized via
// overriding the getter in the SimpleBatchConfiguration
@Override
@Bean
public JobRegistry jobRegistry() throws Exception {
	return new MapJobRegistry();
}
...
```

有两种方法可以自动填充`JobRegistry`:使用 Bean 后处理器和使用注册商生命周期组件。这两种机制在下面的部分中进行了描述。

##### jobregistrybeanpostprocessor

这是一个 Bean 后处理器，它可以在创建所有作业时注册它们。

下面的示例展示了如何为 XML 中定义的作业包括`JobRegistryBeanPostProcessor`:

XML 配置

```
<bean id="jobRegistryBeanPostProcessor" class="org.spr...JobRegistryBeanPostProcessor">
    <property name="jobRegistry" ref="jobRegistry"/>
</bean>
```

下面的示例展示了如何为在 Java 中定义的作业包括`JobRegistryBeanPostProcessor`:

Java 配置

```
@Bean
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor() {
    JobRegistryBeanPostProcessor postProcessor = new JobRegistryBeanPostProcessor();
    postProcessor.setJobRegistry(jobRegistry());
    return postProcessor;
}
```

虽然这不是严格必要的，但是在示例中的后处理器已经被赋予了一个 ID，以便它可以被包括在子上下文中（例如作为父 Bean 定义），并导致在那里创建的所有作业也被自动注册。

##### `AutomaticJobRegistrar`

这是一个生命周期组件，它创建子上下文，并在创建这些上下文时从这些上下文注册作业。这样做的一个好处是，虽然子上下文中的作业名称在注册表中仍然必须是全局唯一的，但它们的依赖项可能具有“自然”名称。因此，例如，你可以创建一组 XML 配置文件，每个配置文件只具有一个作业，但所有配置文件都具有具有具有相同 Bean 名称的`ItemReader`的不同定义，例如“reader”。如果将所有这些文件导入到相同的上下文中，则读写器定义将发生冲突并相互覆盖，但是使用自动注册器可以避免这种情况。这使得集成来自应用程序的独立模块的作业变得更加容易。

下面的示例展示了如何为 XML 中定义的作业包括`AutomaticJobRegistrar`:

XML 配置

```
<bean class="org.spr...AutomaticJobRegistrar">
   <property name="applicationContextFactories">
      <bean class="org.spr...ClasspathXmlApplicationContextsFactoryBean">
         <property name="resources" value="classpath*:/config/job*.xml" />
      </bean>
   </property>
   <property name="jobLoader">
      <bean class="org.spr...DefaultJobLoader">
         <property name="jobRegistry" ref="jobRegistry" />
      </bean>
   </property>
</bean>
```

下面的示例展示了如何为在 Java 中定义的作业包括`AutomaticJobRegistrar`:

Java 配置

```
@Bean
public AutomaticJobRegistrar registrar() {

    AutomaticJobRegistrar registrar = new AutomaticJobRegistrar();
    registrar.setJobLoader(jobLoader());
    registrar.setApplicationContextFactories(applicationContextFactories());
    registrar.afterPropertiesSet();
    return registrar;

}
```

注册商有两个强制属性，一个是`ApplicationContextFactory`的数组（这里是从方便的工厂 Bean 创建的），另一个是`JobLoader`。`JobLoader`负责管理子上下文的生命周期，并在`JobRegistry`中注册作业。

`ApplicationContextFactory`负责创建子上下文，最常见的用法是使用`ClassPathXmlApplicationContextFactory`。这个工厂的一个特性是，默认情况下，它会将一些配置从父上下文复制到子上下文。因此，例如，如果它应该与父配置相同，则不必重新定义子配置中的`PropertyPlaceholderConfigurer`或 AOP 配置。

如果需要，`AutomaticJobRegistrar`可以与`JobRegistryBeanPostProcessor`一起使用（只要`DefaultJobLoader`也可以使用）。例如，如果在主父上下文和子位置中定义了作业，那么这可能是可取的。

#### joboperator

如前所述，`JobRepository`提供对元数据的增删改查操作，而`JobExplorer`提供对元数据的只读操作。然而，当这些操作一起用来执行常见的监视任务时，它们是最有用的，例如停止、重新启动或汇总作业，就像批处理操作符通常做的那样。 Spring 批处理通过`JobOperator`接口提供这些类型的操作：

```
public interface JobOperator {

    List<Long> getExecutions(long instanceId) throws NoSuchJobInstanceException;

    List<Long> getJobInstances(String jobName, int start, int count)
          throws NoSuchJobException;

    Set<Long> getRunningExecutions(String jobName) throws NoSuchJobException;

    String getParameters(long executionId) throws NoSuchJobExecutionException;

    Long start(String jobName, String parameters)
          throws NoSuchJobException, JobInstanceAlreadyExistsException;

    Long restart(long executionId)
          throws JobInstanceAlreadyCompleteException, NoSuchJobExecutionException,
                  NoSuchJobException, JobRestartException;

    Long startNextInstance(String jobName)
          throws NoSuchJobException, JobParametersNotFoundException, JobRestartException,
                 JobExecutionAlreadyRunningException, JobInstanceAlreadyCompleteException;

    boolean stop(long executionId)
          throws NoSuchJobExecutionException, JobExecutionNotRunningException;

    String getSummary(long executionId) throws NoSuchJobExecutionException;

    Map<Long, String> getStepExecutionSummaries(long executionId)
          throws NoSuchJobExecutionException;

    Set<String> getJobNames();

}
```

上面的操作表示来自许多不同接口的方法，例如`JobLauncher`、`JobRepository`、`JobExplorer`和`JobRegistry`。由于这个原因，所提供的`JobOperator`、`SimpleJobOperator`的实现具有许多依赖性。

下面的示例显示了 XML 中`SimpleJobOperator`的典型 Bean 定义：

```
<bean id="jobOperator" class="org.spr...SimpleJobOperator">
    <property name="jobExplorer">
        <bean class="org.spr...JobExplorerFactoryBean">
            <property name="dataSource" ref="dataSource" />
        </bean>
    </property>
    <property name="jobRepository" ref="jobRepository" />
    <property name="jobRegistry" ref="jobRegistry" />
    <property name="jobLauncher" ref="jobLauncher" />
</bean>
```

下面的示例显示了 Java 中`SimpleJobOperator`的典型 Bean 定义：

```
 /**
  * All injected dependencies for this bean are provided by the @EnableBatchProcessing
  * infrastructure out of the box.
  */
 @Bean
 public SimpleJobOperator jobOperator(JobExplorer jobExplorer,
                                JobRepository jobRepository,
                                JobRegistry jobRegistry) {

	SimpleJobOperator jobOperator = new SimpleJobOperator();

	jobOperator.setJobExplorer(jobExplorer);
	jobOperator.setJobRepository(jobRepository);
	jobOperator.setJobRegistry(jobRegistry);
	jobOperator.setJobLauncher(jobLauncher);

	return jobOperator;
 }
```

|   |如果你在作业存储库上设置了表前缀，请不要忘记在作业资源管理器上也设置它。|
|---|------------------------------------------------------------------------------------------------------|

#### JobParametersIncrementer

关于`JobOperator`的大多数方法都是不言自明的，更详细的解释可以在[接口的 Javadoc](https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/launch/JobOperator.html)上找到。然而，`startNextInstance`方法是值得注意的。这个方法总是会启动一个作业的新实例。如果`JobExecution`中存在严重问题，并且需要从一开始就重新开始工作，那么这将非常有用。与`JobLauncher`不同，`JobLauncher`需要一个新的`JobParameters`对象，如果参数与以前的任何一组参数不同，则该对象将触发一个新的`JobInstance`，`startNextInstance`方法将使用绑定到`JobParametersIncrementer`的`Job`来强制将`Job`转换为一个新实例：

```
public interface JobParametersIncrementer {

    JobParameters getNext(JobParameters parameters);

}
```

`JobParametersIncrementer`的约定是，给定一个[工作参数](#jobParameters)对象，它将通过递增它可能包含的任何必要值来返回“next”JobParameters 对象。这个策略是有用的，因为框架无法知道`JobParameters`的更改是什么，使它成为“下一个”实例。例如，如果`JobParameters`中的唯一值是日期，并且应该创建下一个实例，那么该值应该增加一天吗？或者一周（例如，如果工作是每周一次的话）？对于有助于识别工作的任何数值，也可以这样说，如下所示：

```
public class SampleIncrementer implements JobParametersIncrementer {

    public JobParameters getNext(JobParameters parameters) {
        if (parameters==null || parameters.isEmpty()) {
            return new JobParametersBuilder().addLong("run.id", 1L).toJobParameters();
        }
        long id = parameters.getLong("run.id",1L) + 1;
        return new JobParametersBuilder().addLong("run.id", id).toJobParameters();
    }
}
```

在本例中，使用带有“run.id”键的值来区分`JobInstances`。如果传入的`JobParameters`为空，则可以假定`Job`以前从未运行过，因此可以返回其初始状态。但是，如果不是，则获得旧值，将其递增 1 并返回。

对于 XML 中定义的作业，Incrementer 可以通过名称空间中的’Incrementer’属性与`Job`关联，如下所示：

```
<job id="footballJob" incrementer="sampleIncrementer">
    ...
</job>
```

对于在 Java 中定义的作业，增量程序可以通过构建器中提供的`incrementer`方法与“作业”关联，如下所示：

```
@Bean
public Job footballJob() {
    return this.jobBuilderFactory.get("footballJob")
    				 .incrementer(sampleIncrementer())
    				 ...
                     .build();
}
```

#### 停止工作

`JobOperator`最常见的用例之一是优雅地停止一项工作：

```
Set<Long> executions = jobOperator.getRunningExecutions("sampleJob");
jobOperator.stop(executions.iterator().next());
```

关闭不是立即的，因为无法强制立即关闭，特别是如果当前执行的是框架无法控制的开发人员代码，例如业务服务。但是，一旦将控件返回到框架中，就会将当前`StepExecution`的状态设置为`BatchStatus.STOPPED`，保存它，然后在完成之前对`JobExecution`执行相同的操作。

#### 终止作业

可以重新启动`FAILED`的作业执行（如果`Job`是可重启的）。状态为`ABANDONED`的作业执行将不会被框架重新启动。在步骤执行中，`ABANDONED`状态也被用于在重新启动的作业执行中将其标记为可跳过的：如果作业正在执行，并且遇到了在上一个失败的作业执行中标记`ABANDONED`的步骤，它将进入下一个步骤（由作业流定义和步骤执行退出状态决定）。

如果进程死了（`"kill -9"`或服务器故障），作业当然不在运行，但是`JobRepository`无法知道，因为在进程死之前没有人告诉它。你必须手动告诉它，你知道执行失败或应该被视为已中止（将其状态更改为`FAILED`或`ABANDONED`）-这是一个业务决策，没有办法使其自动化。如果状态不是可重启的，或者你知道重新启动数据是有效的，则仅将状态更改为`FAILED`。 Spring batch admin`JobService`中有一个实用程序来中止作业执行。