# JSR-352 支援

## JSR-352 支持

XMLJavaBoth

截至 Spring，对 JSR-352 的批处理 3.0 支持已经完全实现。本节不是规范本身的替代，而是打算解释 JSR-352 特定概念如何应用于 Spring 批处理。有关 JSR-352 的其他信息可以通过 JCP 在这里找到：

### 关于 Spring 批和 JSR-352 的一般说明

Spring Batch 和 JSR-352 在结构上是相同的。他们俩的工作都是由台阶组成的。它们都有读取器、处理器、编写器和监听器。然而，他们之间的互动却有微妙的不同。例如， Spring 批处理中的`org.springframework.batch.core.SkipListener#onSkipInWrite(S item, Throwable t)`接收两个参数：被跳过的项和导致跳过的异常。相同方法的 JSR-352 版本（`javax.batch.api.chunk.listener.SkipWriteListener#onSkipWriteItem(List<Object> items, Exception ex)`）也接收两个参数。但是，第一个是当前块中所有项的`List`，第二个是导致跳过的`Exception`。由于这些差异，重要的是要注意，在 Spring 批处理中执行作业有两种路径：传统的 Spring 批处理作业或基于 JSR-352 的作业。虽然 Spring 批处理工件（读取器、编写器等）的使用将在使用 JSR-352 的 JSL 配置并使用`JsrJobOperator`执行的作业中进行，但它们的行为将遵循 JSR-352 的规则。还需要注意的是，针对 JSR-352 接口开发的批处理工件将不能在传统的批处理作业中工作。

### 设置

#### 应用程序上下文

Spring 批处理中的所有基于 JSR-352 的作业都由两个应用程序上下文组成。父上下文，它包含与 Spring 批处理的基础结构相关的 bean，例如`JobRepository`、`PlatformTransactionManager`等，以及包含要运行的作业的配置的子上下文。父上下文是通过框架提供的`jsrBaseContext.xml`定义的。可以通过设置`JSR-352-BASE-CONTEXT`系统属性来重写此上下文。

|   |对于属性注入之类的事情，JSR-352 处理器不会处理基本上下文，因此<br/>不需要在此配置额外处理的组件。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

#### 启动基于 JSR-352 的作业

JSR-352 需要一个非常简单的路径来执行批处理作业。以下代码是执行第一批作业所需的全部内容：

```
JobOperator operator = BatchRuntime.getJobOperator();
jobOperator.start("myJob", new Properties());
```

虽然这对开发人员来说很方便，但问题出在细节上。 Spring 批处理引导了一些幕后的基础设施，开发人员可能想要覆盖这些基础设施。下面是第一次调用`BatchRuntime.getJobOperator()`时的引导：

|      *Bean Name*       |                        *Default Configuration*                        |*笔记*|
|------------------------|-----------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|       dataSource       |          Apache DBCP BasicDataSource with configured values.          |默认情况下，HSQLDB 是引导的。|
|  `transactionManager`  |  `org.springframework.jdbc.datasource.DataSourceTransactionManager`   |引用了上面定义的数据源 Bean。|
|A Datasource initializer|                                                                       |这被配置为执行通过`batch.drop.script`和`batch.schema.script`属性配置的脚本。通过<br/>默认值，HSQLDB 的模式脚本被执行。可以通过设置`batch.data.source.init`属性来禁用此行为。|
|     jobRepository      |                  A JDBC based `SimpleJobRepository`.                  |此`JobRepository`使用前面提到的数据源和事务<br/>管理器。模式的表前缀可以通过`batch.table.prefix`属性进行配置（默认为批处理 \_）。|
|      jobLauncher       |   `org.springframework.batch.core.launch.support.SimpleJobLauncher`   |用来启动工作。|
|    batchJobOperator    |   `org.springframework.batch.core.launch.support.SimpleJobOperator`   |`JsrJobOperator`对此进行了包装，以提供其大部分功能。|
|      jobExplorer       |`org.springframework.batch.core.explore.support.JobExplorerFactoryBean`|用于解决`JsrJobOperator`提供的查找功能。|
| jobParametersConverter |    `org.springframework.batch.core.jsr.JsrJobParametersConverter`     |JSR-352 具体实现`JobParametersConverter`。|
|      jobRegistry       | `org.springframework.batch.core.configuration.support.MapJobRegistry` |由`SimpleJobOperator`使用。|
| placeholderProperties  |`org.springframework.beans.factory.config.PropertyPlaceholderConfigure`|加载属性文件`batch-${ENVIRONMENT:hsql}.properties`来配置<br/>上面提到的属性。Environment 是一个系统属性（默认为`hsql`）<br/>，可用于指定当前<br/>支持的任何受支持的数据库 Spring 批处理。|

|   |对于执行基于 JSR-352 的作业，上面的 bean 都不是可选的。所有这些都可以被重写到<br/>，根据需要提供定制的功能。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------|

### 依赖注入

JSR-352 在很大程度上基于 Spring 批编程模型。因此，虽然没有显式地要求正式的依赖注入实现，但是隐含了某种类型的 DI。 Spring 批处理支持用于加载 JSR-352 定义的批处理工件的所有三种方法：

* 实现特定的加载程序： Spring 批处理是建立在 Spring 之上的，因此在 JSR-352 批处理作业中支持 Spring 依赖注入。

* archive loader:JSR-352 定义了一个`batch.xml`文件的存在，该文件提供了逻辑名和类名之间的映射。如果使用此文件，则必须在`/META-INF/`目录中找到该文件。

* 线程上下文类装入器：JSR-352 允许配置通过内联提供完全限定的类名来指定其 JSL 中的批处理工件实现。 Spring 批处理在 JSR-352 配置的作业中也支持这一点。

在基于 JSR-352 的批处理作业中使用 Spring 依赖注入包括使用 Spring 应用程序上下文作为 bean 来配置批处理工件。一旦定义了 bean，作业就可以引用它们，就像在`batch.xml`文件中定义的任何 Bean 一样。

下面的示例展示了如何在 XML 中基于 JSR-352 的批处理作业中使用 Spring 依赖注入：

XML 配置

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://www.springframework.org/schema/beans
                              https://www.springframework.org/schema/beans/spring-beans.xsd
                              http://xmlns.jcp.org/xml/ns/javaee
                              https://xmlns.jcp.org/xml/ns/javaee/jobXML_1_0.xsd">

    <!-- javax.batch.api.Batchlet implementation -->
    <bean id="fooBatchlet" class="io.spring.FooBatchlet">
            <property name="prop" value="bar"/>
    </bean>

    <!-- Job is defined using the JSL schema provided in JSR-352 -->
    <job id="fooJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
        <step id="step1">
            <batchlet ref="fooBatchlet"/>
        </step>
    </job>
</beans>
```

下面的示例展示了如何在 Java 中基于 JSR-352 的批处理作业中使用 Spring 依赖注入：

Java 配置

```
@Configuration
public class BatchConfiguration {

    @Bean
    public Batchlet fooBatchlet() {
        FooBatchlet batchlet = new FooBatchlet();
        batchlet.setProp("bar");
        return batchlet;
       }
}

<?xml version="1.0" encoding="UTF-8"?>
<job id="fooJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
    <step id="step1" >
        <batchlet ref="fooBatchlet" />
    </step>
</job>
```

Spring 上下文（导入等）的组装与 JSR-352 作业一起工作，就像与任何其他基于 Spring 的应用程序一起工作一样。与基于 JSR-352 的作业的唯一不同之处在于，上下文定义的入口点将是/meta-inf/batch-jobs/中找到的作业定义。

要使用线程上下文类装入器方法，你所需要做的就是提供完全限定的类名作为 ref。需要注意的是，当使用此方法或`batch.xml`方法时，引用的类需要一个无参数构造函数，该构造函数将用于创建 Bean。

```
<?xml version="1.0" encoding="UTF-8"?>
<job id="fooJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0">
    <step id="step1" >
        <batchlet ref="io.spring.FooBatchlet" />
    </step>
</job>
```

### 批处理属性

#### 属性支持

JSR-352 允许通过在 JSL 中的配置在作业、步骤和批处理工件级别定义属性。在每个级别上，按以下方式配置批处理属性：

```
<properties>
    <property name="propertyName1" value="propertyValue1"/>
    <property name="propertyName2" value="propertyValue2"/>
</properties>
```

`Properties`可以在任何批处理工件上进行配置。

#### @batchproperty 注释

`Properties`在批处理工件中通过使用`@BatchProperty`和`@Inject`注释（这两个注释都是规范所要求的）注释类字段来引用。根据 JSR-352 的定义，属性的字段必须是字符串类型的。任何类型转换都要由实现开发人员来执行。

可以将`javax.batch.api.chunk.ItemReader`工件配置为具有上述属性块的属性块，并以这样的方式进行访问：

```
public class MyItemReader extends AbstractItemReader {
    @Inject
    @BatchProperty
    private String propertyName1;

    ...
}
```

字段“PropertyName1”的值将是“PropertyValue1”

#### 属性替换

属性替换是通过运算符和简单条件表达式来提供的。一般用法是`#{operator['key']}`。

支持的操作符：

* `jobParameters`:访问启动/重新启动作业的作业参数值。

* `jobProperties`:在 JSL 的作业级别上配置的访问属性。

* `systemProperties`:访问命名的系统属性。

* `partitionPlan`:从一个分区步骤的分区计划中访问命名属性。

```
#{jobParameters['unresolving.prop']}?:#{systemProperties['file.separator']}
```

赋值的左边是期望值，右边是默认值。在前面的示例中，结果将解析为系统属性文件的值。分隔符 #{jobparamets[’unsolving.prop’]}被假定为不可解析。如果两个表达式都不能解析，将返回一个空字符串。可以使用多个条件，这些条件由“；”分隔。

### 处理模型

JSR-352 提供了与 Spring 批处理相同的两个基本处理模型：

* 基于项的处理-使用`javax.batch.api.chunk.ItemReader`、可选`javax.batch.api.chunk.ItemProcessor`和`javax.batch.api.chunk.ItemWriter`。

* 基于任务的处理-使用`javax.batch.api.Batchlet`实现。这种处理模型与当前可用的基于`org.springframework.batch.core.step.tasklet.Tasklet`的处理相同。

#### 基于项目的处理

在此上下文中，基于项的处理是由`ItemReader`读取的项数设置的块大小。要以这种方式配置步骤，请指定`item-count`（默认值为 10），并可选择将`checkpoint-policy`配置为项（这是默认值）。

```
...
<step id="step1">
    <chunk checkpoint-policy="item" item-count="3">
        <reader ref="fooReader"/>
        <processor ref="fooProcessor"/>
        <writer ref="fooWriter"/>
    </chunk>
</step>
...
```

如果选择了基于项的检查点，则支持一个附加属性`time-limit`。这为必须处理指定的项数设置了一个时间限制。如果达到了超时，那么不管`item-count`配置为什么，该块都将完成，到那时已经读取了多少项。

#### 自定义检查点

JSR-352 在步骤“检查点”中调用围绕提交间隔的进程。基于项目的检查点是上面提到的一种方法。然而，在许多情况下，这还不够强大。因此，规范允许通过实现`javax.batch.api.chunk.CheckpointAlgorithm`接口来实现自定义检查点算法。该功能在功能上与 Spring Batch 的自定义完成策略相同。要使用`CheckpointAlgorithm`的实现，请使用自定义`checkpoint-policy`配置你的步骤，如下所示，其中`fooCheckpointer`是指`CheckpointAlgorithm`的实现。

```
...
<step id="step1">
    <chunk checkpoint-policy="custom">
        <checkpoint-algorithm ref="fooCheckpointer"/>
        <reader ref="fooReader"/>
        <processor ref="fooProcessor"/>
        <writer ref="fooWriter"/>
    </chunk>
</step>
...
```

### 运行作业

执行基于 JSR-352 的作业的入口是通过`javax.batch.operations.JobOperator`。 Spring 批处理提供了它自己实现的这个接口（`org.springframework.batch.core.jsr.launch.JsrJobOperator`）。这个实现是通过`javax.batch.runtime.BatchRuntime`加载的。启动基于 JSR-352 的批处理作业的实现如下：

```
JobOperator jobOperator = BatchRuntime.getJobOperator();
long jobExecutionId = jobOperator.start("fooJob", new Properties());
```

上述代码执行以下操作：

* 引导基本`ApplicationContext`:为了提供批处理功能，框架需要一些基础结构的引导。这在每个 JVM 中发生一次。引导的组件类似于`@EnableBatchProcessing`提供的组件。可以在`JsrJobOperator`的 Javadoc 中找到具体的详细信息。

* 为请求的作业加载`ApplicationContext`:在上面的示例中，框架在/meta-inf/batch-jobs 中查找一个名为 foojob.xml 的文件，并加载一个上下文，该上下文是前面提到的共享上下文的子上下文。

* 启动作业：在上下文中定义的作业将异步执行。将返回`JobExecution’s`ID。

|   |所有基于 JSR-352 的批处理作业都是异步执行的。|
|---|---------------------------------------------------------|

当使用`JobOperator#start`调用`SimpleJobOperator`时， Spring 批处理确定调用是初始运行还是对先前执行的运行的重试。使用基于 JSR-352 的`JobOperator#start(String jobXMLName, Properties jobParameters)`，框架将始终创建一个新的 JobInstance（JSR-352 作业参数是不标识的）。为了重新启动作业，需要调用`JobOperator#restart(long executionId, Properties restartParameters)`。

### 上下文

JSR-352 定义了两个上下文对象，用于与批处理工件中的作业或步骤的元数据交互：`javax.batch.runtime.context.JobContext`和`javax.batch.runtime.context.StepContext`。这两个都可以在任何步骤级别的工件（`Batchlet`，`ItemReader`等）中使用，而`JobContext`也可以用于作业级别工件（例如`JobListener`）。

要获得对当前作用域中`JobContext`或`StepContext`的引用，只需使用`@Inject`注释：

```
@Inject
JobContext jobContext;
```

|   |@autowire for JSR-352contexts<br/><br/>使用 Spring 的 @autowire 不支持这些上下文的注入。|
|---|----------------------------------------------------------------------------------------------------------------------|

在 Spring 批处理中，`JobContext`和`StepContext`分别包装其对应的执行对象（`JobExecution`和`StepExecution`）。通过`StepContext#setPersistentUserData(Serializable data)`存储的数据存储在 Spring 批中`StepExecution#executionContext`。

### 阶跃流

在基于 JSR-352 的作业中，步骤流程的工作方式与 Spring 批处理中的工作方式类似。然而，这里有几个细微的区别：

* 决策是步骤——在常规的 Spring 批作业中，决策是一种状态，它不具有独立的`StepExecution`，也不具有伴随整个步骤而来的任何权利和责任。然而，在 JSR-352 中，一个决策就像其他任何步骤一样是一个步骤，并且将表现为任何其他步骤（事务性，它得到`StepExecution`等）。这意味着，在重启过程中，它们与其他任何步骤一样受到同等对待。

* `next`属性和步骤转换-在常规作业中，允许在相同的步骤中同时出现这些转换。JSR-352 允许在相同的步骤中使用它们，并在计算中优先使用 Next 属性。

* 转换元素排序--在标准 Spring 批处理作业中，转换元素从最特定的到最不特定的进行排序，并按照该顺序进行评估。JSR-352 作业按照转换元素在 XML 中指定的顺序对其进行评估。

### 缩放 JSR-352 批处理作业

Spring 传统的批处理作业有四种缩放方式（最后两种能够跨多个 JVM 执行）：

* 拆分-并行运行多个步骤。

* 多个线程-通过多个线程执行一个步骤。

* 分区-将数据划分为并行处理（Manager/Worker）。

* 远程分块-远程执行处理器逻辑块.

JSR-352 提供了两种缩放批处理作业的选项。这两个选项都只支持一个 JVM:

* 拆分-与 Spring 批相同

* 分区-概念上与 Spring 批处理相同，但实现方式略有不同。

#### 分区

从概念上讲，JSR-352 中的分区与 Spring 批处理中的分区相同。元数据被提供给每个工作人员，以标识要处理的输入，工作人员在完成后将结果报告给经理。然而，也有一些重要的不同之处：

* 分区`Batchlet`-这将在多个线程上运行配置的`Batchlet`的多个实例。每个实例都有自己的一组属性，如 JSL 或`PartitionPlan`提供的

* `PartitionPlan`-通过 Spring 批处理的分区，为每个分区提供了`ExecutionContext`。在 JSR-352 中，单个`javax.batch.api.partition.PartitionPlan`被提供了一个`Properties`的数组，为每个分区提供元数据。

* `PartitionMapper`-JSR-352 提供了生成分区元数据的两种方法。一种是通过 JSL（分区属性）。第二个是通过`javax.batch.api.partition.PartitionMapper`接口实现的。在功能上，该接口类似于 Spring Batch 提供的`org.springframework.batch.core.partition.support.Partitioner`接口，因为它提供了一种以编程方式生成用于分区的元数据的方法。

* `StepExecutions`-在 Spring 批处理中，分区步骤以 Manager/Worker 的形式运行。在 JSR-352 中，发生了相同的配置。然而，工人的步骤并没有得到正式的`StepExecutions`。因此，对`JsrJobOperator#getStepExecutions(long jobExecutionId)`的调用将只返回 Manager 的`StepExecution`。

|   |子`StepExecutions`仍然存在于作业存储库中，并且通过`JobExplorer`可用<br/>。|
|---|-------------------------------------------------------------------------------------------------------------|

* 补偿逻辑-由于 Spring 批处理使用步骤实现了分区的 Manager/Worker 逻辑，所以如果出现问题，`StepExecutionListeners`可以用来处理补偿逻辑。然而，由于 Workers JSR-352 提供了一个其他组件的集合，因此能够在发生错误时提供补偿逻辑并动态设置退出状态。这些组成部分包括：

|             *Artifact Interface*             |*说明*|
|----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
|`javax.batch.api.partition.PartitionCollector`|提供了一种方法，用于将信息发送回<br/>管理器的工作步骤。每个工作线程有一个实例。|
|`javax.batch.api.partition.PartitionAnalyzer` |端点接收由`PartitionCollector`收集的信息，以及从一个完整的分区获得的结果<br/>状态。|
| `javax.batch.api.partition.PartitionReducer` |提供为分区<br/>步骤提供补偿逻辑的能力。|

### 测试

由于所有基于 JSR-352 的作业都是异步执行的，因此很难确定作业何时完成。为了帮助进行测试， Spring Batch 提供了`org.springframework.batch.test.JsrTestUtils`。这个实用程序类提供了启动作业、重新启动作业并等待作业完成的功能。作业完成后，将返回相关的`JobExecution`。