On this page
12. JSR-352 Support
从 Spring Batch 3.0 开始,已经完全实现了对 JSR-352 的支持。本部分不是规范本身的替代,而是打算解释 JSR-352 特定概念如何应用于 Spring Batch。有关 JSR-352 的其他信息,可以通过 JCP 在这里找到:https://jcp.org/en/jsr/detail?id=352
12.1 一般说明 Spring Batch 和 JSR-352
Spring Batch 和 JSR-352 在结构上相同。他们俩的工作都是由步骤组成的。他们都有阅 Reader,处理者,Writer 和 Listener。但是,它们之间的交互作用略有不同。例如,Spring Batch 中的org.springframework.batch.core.SkipListener#onSkipInWrite(S item, Throwable t)
接收两个参数:被跳过的 Item 和导致跳过的异常。相同方法(javax.batch.api.chunk.listener.SkipWriteListener#onSkipWriteItem(List<Object> items, Exception ex)
)的 JSR-352 版本也接收两个参数。但是,第一个是当前块中所有 Item 的List
,第二个是导致跳过的Exception
。由于存在这些差异,因此需要特别注意的是,在 Spring Batch 中有两种执行作业的路径:传统的 Spring Batch 作业或基于 JSR-352 的作业。尽管使用 Spring Batch 工件(读取器,写入器等)将在通过 JSR-352 的 JSL 配置并通过JsrJobOperator
执行的作业中工作,但它们将按照 JSR-352 的规则运行。还需要注意的是,针对 JSR-352 接口开发的批处理工件将无法在传统的 Spring Batch 作业中使用。
12.2 Setup
12.2.1 应用程序上下文
Spring Batch 中所有基于 JSR-352 的作业均包含两个应用程序上下文。一个父上下文,其中包含与 Spring Batch 基础结构(如JobRepository
,PlatformTransactionManager
等)相关的 bean,以及一个子上下文,该子上下文包含要运行的作业的配置。父上下文是通过框架提供的baseContext.xml
定义的。可以通过JSR-352-BASE-CONTEXT
系统属性覆盖此上下文。
Note
JSR-352 处理器不对基本上下文进行诸如属性注入之类的处理,因此不需要在其中配置任何需要额外处理的组件。
12.2.2 启动基于 JSR-352 的作业
JSR-352 需要执行批处理作业的非常简单的路径。以下代码是执行第一个批处理作业所需的全部:
JobOperator operator = BatchRuntime.getJobOperator();
jobOperator.start("myJob", new Properties());
虽然这对开发人员很方便,但细节在于魔鬼。 Spring Batch 在开发人员可能想覆盖的幕后引导了一些基础架构。以下是首次调用BatchRuntime.getJobOperator()
时的引导程序:
Bean Name | Default Configuration | Notes |
dataSource | 具有配置值的 Apache DBCP BasicDataSource。 | 默认情况下,HSQLDB 被引导。 |
transactionManager |
org.springframework.jdbc.datasource.DataSourceTransactionManager |
引用上面定义的 dataSource bean。 |
数据源初始化器 | 它被配置为执行通过batch.drop.script 和batch.schema.script 属性配置的脚本。默认情况下,将执行 HSQLDB 的架构脚本。可以通过batch.data.source.init 属性禁用此行为。 |
|
jobRepository | 基于 JDBC 的SimpleJobRepository 。 |
JobRepository 使用前面提到的数据源和事务 Management 器。可通过batch.table.prefix 属性配置模式的表前缀(默认为 BATCH_)。 |
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 以配置上述属性。 ENVIRONMENT 是一个系统属性(默认为 hsql),可用于指定 Spring Batch 当前支持的任何受支持数据库。 |
Note
对于执行基于 JSR-352 的作业,上述所有 bean 都不是可选的。所有这些都可以覆盖以根据需要提供自定义功能。
12.3 依赖注入
JSR-352 很大程度上基于 Spring Batch 编程模型。这样,虽然没有明确要求正式的依赖注入实现,但暗含了某种形式的 DI。 Spring Batch 支持三种用于加载由 JSR-352 定义的批处理工件的方法:
特定于实现的加载器-Spring Batch 是基于 Spring 构建的,因此支持 JSR-352 批处理作业中的 Spring 依赖项注入。
存档加载器-JSR-352 定义了现成的 batch.xml 文件,该文件提供了逻辑名和类名之间的 Map。如果使用此文件,则必须在/ META-INF /目录中找到该文件。
线程上下文类加载器-JSR-352 允许配置通过内联提供完全限定的类名来在其 JSL 中指定批处理工件实现。 Spring Batch 在 JSR-352 配置的作业中也支持此功能。
在基于 JSR-352 的批处理作业中使用 Spring 依赖项注入包括使用 Spring 应用程序上下文作为 bean 配置批处理工件。一旦定义了 bean,作业就可以引用它们,就像在 batch.xml 中定义的任何 bean 一样。
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://xmlns.jcp.org/xml/ns/javaee
http://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>
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>
12.4 批处理属性
12.4.1 财产支持
JSR-352 允许通过 JSL 中的配置在 Job,Step 和 Batch 工件级别上定义属性。批处理属性通过以下方式在每个级别上配置:
<properties>
<property name="propertyName1" value="propertyValue1"/>
<property name="propertyName2" value="propertyValue2"/>
</properties>
可以在任何批处理工件上配置属性。
12.4.2 @BatchProperty 注解
通过使用@BatchProperty
和@Inject
注解对类字段进行注解,批处理工件中引用了属性(规范要求这两个注解)。根据 JSR-352 的定义,属性的字段必须为 String 类型。任何类型的转换都由实施开发人员来执行。
javax.batch.api.chunk.ItemReader
工件可以配置一个属性块,例如上述的属性块,并按以下方式访问:
public class MyItemReader extends AbstractItemReader {
@Inject
@BatchProperty
private String propertyName1;
...
}
字段“ propertyName1”的值将为“ propertyValue1”
12.4.3 财产替代
通过操作符和简单的条件表达式提供属性替换。一般用法是#\ {operator['key']}。
Supported operators:
jobParameters-访问启动/重新启动作业的作业参数值。
jobProperties-访问在 JSL 的作业级别配置的属性。
systemProperties-访问命名的系统属性。
partitionPlan-从分区步骤的分区计划中访问命名属性。
#{jobParameters['unresolving.prop']}?:#{systemProperties['file.separator']}
分配的左侧是期望值,右侧是默认值。在此示例中,结果将解析为系统属性 file.separator 的值,因为假定#\ {jobParameters['unresolving.prop']}不可解析。如果两个表达式都无法解析,则将返回一个空的 String。可以使用多个条件,以“;”分隔。
12.5 处理模型
JSR-352 提供了与 Spring Batch 相同的两个基本处理模型:
基于 Item 的处理-使用
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
的处理相同。
12.5.1 基于 Item 的处理
在这种情况下,基于 Item 的处理是由ItemReader
读取的 Item 数设置的块大小。要以这种方式配置步骤,请指定item-count
(默认值为 10),并可以选择将checkpoint-policy
配置为 Item(默认值)。
...
<step id="step1">
<chunk checkpoint-policy="item" item-count="3">
<reader ref="fooReader"/>
<processor ref="fooProcessor"/>
<writer ref="fooWriter"/>
</chunk>
</step>
...
如果选择了基于 Item 的检查点,则支持附加属性time-limit
。这为必须处理指定的 Item 数设置了时间限制。如果达到了超时,那么无论item-count
被配置为什么,块都将完成,但是届时将读取许多项。
12.5.2 自定义检查点
JSR-352 在“检查点”步骤内在提交间隔附近调用该过程。如上所述,基于 Item 的检查点是一种方法。但是,这在许多情况下不够鲁棒。因此,该规范允许通过实现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>
...
12.6 执行工作
通过javax.batch.operations.JobOperator
可以执行基于 JSR-352 的作业。 Spring Batch 为此接口(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
的 ID 将返回。
Note
所有基于 JSR-352 的批处理作业均异步执行。
当使用SimpleJobOperator
调用JobOperator#start
时,Spring Batch 将确定该调用是初始运行还是重试先前执行的运行。使用基于 JSR-352 的JobOpeator#start(String jobXMLName, Properties jobParameters)
,框架将始终创建一个新的JobInstance
(JSR-352 作业参数无法识别)。为了重新启动作业,需要调用JobOperator#restart(long executionId, Properties restartParameters)
。
12.7 Contexts
JSR-352 定义了两个上下文对象,它们用于与批处理工件中的作业或步骤的元数据进行交互:javax.batch.runtime.context.JobContext
和javax.batch.runtime.context.StepContext
。两者都可以在任何步骤级别的工件(Batchlet
,ItemReader
等)中使用,而JobContext
也可用于作业级别的工件(例如JobListener
)。
要获得对当前范围内的JobContext
或StepContext
的引用,只需使用@Inject
注解:
@Inject
JobContext jobContext;
Note
这些上下文的注入不支持使用 Spring 的@Autowire。
在 Spring Batch 中,JobContext
和StepContext
包装了它们相应的执行对象(分别为JobExecution
和StepExecution
)。通过StepContext#persistent#setPersistentUserData(Serializable data)
存储的数据存储在 Spring Batch StepExecution#executionContext
中。
12.8 步骤流程
在基于 JSR-352 的作业中,步骤流程的工作原理与 Spring Batch 中的步骤相似。但是,有一些细微的差异:
决策就是步骤-在常规的 Spring Batch 工作中,决策是没有独立的
StepExecution
或没有完整步骤所伴随的任何权利和责任的状态。但是,对于 JSR-352,决策是与其他任何步骤一样,其行为也将与其他任何步骤一样(可 Transactional,它得到StepExecution
等)。这意味着它们也与其他任何重新启动步骤一样。next
属性和步骤过渡-在常规作业中,允许它们在同一步骤中一起出现。 JSR-352 允许它们在同一步骤中使用,并且下一个属性在评估中优先使用。过渡元素排序-在标准 Spring Batch 作业中,过渡元素从最具体到最不具体排序,并按该 Sequences 进行评估。 JSR-352 作业按照 XML 中指定的 Sequences 评估过渡元素。
12.9 扩展 JSR-352 批处理作业
传统的 Spring Batch 作业具有四种扩展方式(后两种可以跨多个 JVM 执行):
拆分-并行运行多个步骤。
多线程-通过多线程执行单个步骤。
分区-划分数据以进行并行处理(主/从)。
远程分块-远程执行处理器逻辑。
JSR-352 提供了两个用于扩展批处理作业的选项。这两个选项仅支持单个 JVM:
拆分-与 Spring Batch 相同
分区-从概念上讲与 Spring Batch 相同,但是实现方面略有不同。
12.9.1 Partitioning
从概念上讲,JSR-352 中的分区与 Spring Batch 中的分区相同。元数据提供给每个从属设备,以标识要处理的 Importing,从属设备在完成后将结果报告给主设备。但是,有一些重要的区别:
已分区
Batchlet
-这将在多个线程上运行已配置Batchlet
的多个实例。每个实例将具有由 JSL 或PartitionPlan
提供的自己的一组属性。PartitionPlan
-使用 Spring Batch 的分区,为每个分区提供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
接口,因为它提供了一种以编程方式生成用于分区的元数据的方法。StepExecution
s-在 Spring Batch 中,分区步骤以主/从身份运行。在 JSR-352 中,会发生相同的配置。但是,从属步骤未获得官方StepExecution
s。因此,对JsrJobOperator#getStepExecutions(long jobExecutionId)
的调用只会为主机返回StepExecution
。
Note
子StepExecution
仍然存在于作业存储库中,可以通过JobExplorer
和 Spring Batch Admin 使用。
- 补偿逻辑-由于 Spring Batch 使用步骤实现分区的主/从逻辑,因此如果出现问题,可以使用
StepExecutionListener
s 处理补偿逻辑。但是,由于从站 JSR-352 提供了其他组件的集合,因此能够在发生错误时提供补偿逻辑并动态设置退出状态。这些组件包括:
Artifact Interface | Description |
javax.batch.api.partition.PartitionCollector |
提供一种用于从属步骤将信息发送回主控方的方法。每个从属线程有一个实例。 |
javax.batch.api.partition.PartitionAnalyzer |
端点,该端点接收PartitionCollector 收集的信息以及来自已完成分区的结果状态。 |
javax.batch.api.partition.PartitionReducer |
提供为分区步骤提供补偿逻辑的功能。 |
12.10 Testing
由于所有基于 JSR-352 的作业都是异步执行的,因此很难确定作业何时完成。为了帮助测试,Spring Batch 提供了org.springframework.batch.core.jsr.JsrTestUtils
。该 Util 类提供了启动作业,重新启动作业并 await 其完成的功能。作业完成后,将返回关联的JobExecution
。