step.md 69.7 KB
Newer Older
dallascao's avatar
dallascao 已提交
1 2
# 配置一个步骤

茶陵後's avatar
茶陵後 已提交
3
## 配置`Step`
dallascao's avatar
dallascao 已提交
4 5 6 7 8 9 10 11 12

XMLJavaBoth

正如[领域章节](domain.html#domainLanguageOfBatch)中所讨论的,`Step`是一个域对象,它封装了批处理作业的一个独立的、连续的阶段,并包含定义和控制实际批处理所需的所有信息。这必然是一个模糊的描述,因为任何给定的`Step`的内容都是由编写`Job`的开发人员自行决定的。a`Step`可以是简单的,也可以是复杂的,正如开发人员所希望的那样。一个简单的`Step`可能会将文件中的数据加载到数据库中,只需要很少或不需要代码(取决于使用的实现)。更复杂的`Step`可能具有复杂的业务规则,作为处理的一部分,如下图所示:

![Step](./images/step.png)

图 1。步骤

茶陵後's avatar
茶陵後 已提交
13
### 面向块的处理
dallascao's avatar
dallascao 已提交
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63

Spring 批处理在其最常见的实现中使用了一种“面向块”的处理风格。面向块的处理指的是一次读取一个数据,并创建在事务边界内写出的“块”。一旦读取的项数等于提交间隔,`ItemWriter`就会写出整个块,然后提交事务。下图显示了这个过程:

![面向块的处理](./images/chunk-oriented-processing.png)

图 2。面向块的处理

下面的伪代码以简化的形式显示了相同的概念:

```
List items = new Arraylist();
for(int i = 0; i < commitInterval; i++){
    Object item = itemReader.read();
    if (item != null) {
        items.add(item);
    }
}
itemWriter.write(items);
```

面向块的步骤还可以配置一个可选的`ItemProcessor`来处理项,然后将它们传递给`ItemWriter`。下图显示了在步骤中注册`ItemProcessor`时的过程:

![基于项目处理器的面向块处理](./images/chunk-oriented-processing-with-item-processor.png)

图 3。基于项目处理器的面向块处理

下面的伪代码展示了如何以简化的形式实现这一点:

```
List items = new Arraylist();
for(int i = 0; i < commitInterval; i++){
    Object item = itemReader.read();
    if (item != null) {
        items.add(item);
    }
}

List processedItems = new Arraylist();
for(Object item: items){
    Object processedItem = itemProcessor.process(item);
    if (processedItem != null) {
        processedItems.add(processedItem);
    }
}

itemWriter.write(processedItems);
```

有关项处理器及其用例的更多详细信息,请参阅[项目处理](processor.html#itemProcessor)部分。

茶陵後's avatar
茶陵後 已提交
64
#### 配置`Step`
dallascao's avatar
dallascao 已提交
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135

尽管`Step`所需依赖项的列表相对较短,但它是一个非常复杂的类,可能包含许多协作者。

为了简化配置,可以使用 Spring 批 XML 命名空间,如以下示例所示:

XML 配置

```
<job id="sampleJob" job-repository="jobRepository">
    <step id="step1">
        <tasklet transaction-manager="transactionManager">
            <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
        </tasklet>
    </step>
</job>
```

在使用 Java 配置时,可以使用 Spring 批处理构建器,如以下示例所示:

Java 配置

```
/**
 * Note the JobRepository is typically autowired in and not needed to be explicitly
 * configured
 */
@Bean
public Job sampleJob(JobRepository jobRepository, Step sampleStep) {
    return this.jobBuilderFactory.get("sampleJob")
    			.repository(jobRepository)
                .start(sampleStep)
                .build();
}

/**
 * Note the TransactionManager is typically autowired in and not needed to be explicitly
 * configured
 */
@Bean
public Step sampleStep(PlatformTransactionManager transactionManager) {
	return this.stepBuilderFactory.get("sampleStep")
				.transactionManager(transactionManager)
				.<String, String>chunk(10)
				.reader(itemReader())
				.writer(itemWriter())
				.build();
}
```

上面的配置包括创建面向项的步骤所需的唯一依赖项:

* `reader`:提供处理项的`ItemReader`

* `writer`:处理由`ItemReader`提供的项的`ItemWriter`

* `transaction-manager`: Spring 的`PlatformTransactionManager`,在处理过程中开始并提交事务。

* `transactionManager`: Spring 的`PlatformTransactionManager`,在处理过程中开始并提交事务。

* `job-repository`:`JobRepository`的特定于 XML 的名称,该名称在处理过程中(就在提交之前)定期存储`StepExecution``ExecutionContext`。对于内联`<step/>`(在`<job/>`中定义的),它是`<job/>`元素上的一个属性。对于独立的`<step/>`,它被定义为 \<tasklet/\>的属性。

* `repository`:`JobRepository`的特定于 Java 的名称,该名称在处理过程中(就在提交之前)定期存储`StepExecution``ExecutionContext`

* `commit-interval`:在提交事务之前要处理的项数的 XML 特定名称。

* `chunk`:依赖项的特定于 Java 的名称,该名称指示这是一个基于项的步骤,以及在提交事务之前要处理的项数。

需要注意的是,`job-repository`默认为`jobRepository``transaction-manager`默认为`transactionManager`。而且,`ItemProcessor`是可选的,因为该项可以直接从阅读器传递给编写器。

需要注意的是,`repository`默认为`jobRepository``transactionManager`默认为`transactionManager`(都是通过`@EnableBatchProcessing`中的基础设施提供的)。而且,`ItemProcessor`是可选的,因为该项可以直接从阅读器传递给编写器。

茶陵後's avatar
茶陵後 已提交
136
#### 从父节点继承`Step`
dallascao's avatar
dallascao 已提交
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161

如果一组`Steps`共享类似的配置,那么定义一个“父”`Step`可能是有帮助的,具体的`Steps`可以从中继承属性。与 Java 中的类继承类似,“child”`Step`将其元素和属性与父元素和属性结合在一起。子程序还重写父程序的任何`Steps`

在下面的示例中,`Step`,“concreteStep1”,继承自“parentstep”。它用“itemreader”、“itemprocessor”、“itemwriter”、`startLimit=5``allowStartIfComplete=true`实例化。此外,`commitInterval`是“5”,因为它被“concreteStep1”`Step`覆盖,如以下示例所示:

```
<step id="parentStep">
    <tasklet allow-start-if-complete="true">
        <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
    </tasklet>
</step>

<step id="concreteStep1" parent="parentStep">
    <tasklet start-limit="5">
        <chunk processor="itemProcessor" commit-interval="5"/>
    </tasklet>
</step>
```

在 Job 元素中的 Step 上仍然需要`id`属性。这有两个原因:

* 在持久化`StepExecution`时,使用`id`作为步骤名。如果在作业中的多个步骤中引用了相同的独立步骤,则会发生错误。

* 在创建工作流时,如本章后面所述,`next`属性应该指代工作流中的步骤,而不是独立的步骤。

茶陵後's avatar
茶陵後 已提交
162
##### 摘要`Step`
dallascao's avatar
dallascao 已提交
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181

有时,可能需要定义不是完整的`Step`配置的父`Step`。例如,如果`reader``writer``tasklet`属性在`Step`配置中被保留,则初始化失败。如果必须在没有这些属性的情况下定义父属性,那么应该使用`abstract`属性。`abstract``Step`只是扩展,不是实例化。

在下面的示例中,如果不声明`Step``abstractParentStep`为抽象,则不会对其进行实例化。`Step`、“ConcreteStep2”有“itemreader”、“itemwriter”和 commit-interval=10。

```
<step id="abstractParentStep" abstract="true">
    <tasklet>
        <chunk commit-interval="10"/>
    </tasklet>
</step>

<step id="concreteStep2" parent="abstractParentStep">
    <tasklet>
        <chunk reader="itemReader" writer="itemWriter"/>
    </tasklet>
</step>
```

茶陵後's avatar
茶陵後 已提交
182
##### 合并列表
dallascao's avatar
dallascao 已提交
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204

`Steps`上的一些可配置元素是列表,例如`<listeners/>`元素。如果父元素和子元素`Steps`都声明一个`<listeners/>`元素,那么子元素的列表将覆盖父元素的列表。为了允许子元素向父元素定义的列表中添加额外的侦听器,每个 List 元素都具有`merge`属性。如果元素指定`merge="true"`,那么子元素的列表将与父元素的列表合并,而不是覆盖它。

在下面的示例中,使用两个侦听器创建`Step`“concreteStep3”:`listenerOne``listenerTwo`:

```
<step id="listenersParentStep" abstract="true">
    <listeners>
        <listener ref="listenerOne"/>
    <listeners>
</step>

<step id="concreteStep3" parent="listenersParentStep">
    <tasklet>
        <chunk reader="itemReader" writer="itemWriter" commit-interval="5"/>
    </tasklet>
    <listeners merge="true">
        <listener ref="listenerTwo"/>
    <listeners>
</step>
```

茶陵後's avatar
茶陵後 已提交
205
#### 提交间隔
dallascao's avatar
dallascao 已提交
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246

如前所述,一个步骤读入并写出项,并使用提供的`PlatformTransactionManager`定期提交。如果`commit-interval`为 1,则在写入每个单独的项后提交。在许多情况下,这是不理想的,因为开始和提交事务是昂贵的。理想情况下,最好是在每个事务中处理尽可能多的项,这完全取决于所处理的数据类型以及与该步骤交互的资源。因此,可以配置在提交中处理的项数。

下面的示例显示了一个`step`,其`tasklet``commit-interval`值为 10,因为它将在 XML 中定义:

XML 配置

```
<job id="sampleJob">
    <step id="step1">
        <tasklet>
            <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
        </tasklet>
    </step>
</job>
```

下面的示例显示了一个`step`,其`tasklet`的值`commit-interval`为 10,这将在 Java 中定义:

Java 配置

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

@Bean
public Step step1() {
	return this.stepBuilderFactory.get("step1")
				.<String, String>chunk(10)
				.reader(itemReader())
				.writer(itemWriter())
				.build();
}
```

在前面的示例中,在每个事务中处理 10 个项目。在处理的开始,事务就开始了。此外,每次在`read`上调用`ItemReader`时,计数器都会递增。当它达到 10 时,聚合项的列表被传递给`ItemWriter`,事务被提交。

茶陵後's avatar
茶陵後 已提交
247
#### 配置用于重新启动的`Step`
dallascao's avatar
dallascao 已提交
248 249 250

在“[配置和运行作业](job.html#configureJob)”小节中,讨论了重新启动`Job`。重启对步骤有很多影响,因此,可能需要一些特定的配置。

茶陵後's avatar
茶陵後 已提交
251
##### 设置启动限制
dallascao's avatar
dallascao 已提交
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284

在许多情况下,你可能希望控制`Step`可以启动的次数。例如,可能需要对特定的`Step`进行配置,使其仅运行一次,因为它会使一些必须手动修复的资源失效,然后才能再次运行。这是在步骤级别上可配置的,因为不同的步骤可能有不同的需求。可以只执行一次的`Step`可以作为同一`Job`的一部分存在,也可以作为可以无限运行的`Step`的一部分存在。

下面的代码片段展示了一个 XML 中的 Start Limit 配置示例:

XML 配置

```
<step id="step1">
    <tasklet start-limit="1">
        <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
    </tasklet>
</step>
```

下面的代码片段展示了一个 Java 中的 Start Limit 配置示例:

Java 配置

```
@Bean
public Step step1() {
	return this.stepBuilderFactory.get("step1")
				.<String, String>chunk(10)
				.reader(itemReader())
				.writer(itemWriter())
				.startLimit(1)
				.build();
}
```

前面示例中所示的步骤只能运行一次。试图再次运行它将导致抛出`StartLimitExceededException`。请注意,start-limit 的默认值是`Integer.MAX_VALUE`

茶陵後's avatar
茶陵後 已提交
285
##### 重新启动已完成的`Step`
dallascao's avatar
dallascao 已提交
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316

在可重启作业的情况下,无论第一次是否成功,都可能有一个或多个应该始终运行的步骤。例如,验证步骤或`Step`在处理前清理资源。在对重新启动的作业进行正常处理期间,跳过状态为“已完成”的任何步骤,这意味着该步骤已成功完成。将`allow-start-if-complete`设置为“true”会重写此项,以便该步骤始终运行。

下面的代码片段展示了如何在 XML 中定义一个可重启作业:

XML 配置

```
<step id="step1">
    <tasklet allow-start-if-complete="true">
        <chunk reader="itemReader" writer="itemWriter" commit-interval="10"/>
    </tasklet>
</step>
```

下面的代码片段展示了如何在 Java 中定义一个可重启作业:

Java 配置

```
@Bean
public Step step1() {
	return this.stepBuilderFactory.get("step1")
				.<String, String>chunk(10)
				.reader(itemReader())
				.writer(itemWriter())
				.allowStartIfComplete(true)
				.build();
}
```

茶陵後's avatar
茶陵後 已提交
317
##### `Step`重新启动配置示例
dallascao's avatar
dallascao 已提交
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420

下面的 XML 示例展示了如何将作业配置为具有可以重新启动的步骤:

XML 配置

```
<job id="footballJob" restartable="true">
    <step id="playerload" next="gameLoad">
        <tasklet>
            <chunk reader="playerFileItemReader" writer="playerWriter"
                   commit-interval="10" />
        </tasklet>
    </step>
    <step id="gameLoad" next="playerSummarization">
        <tasklet allow-start-if-complete="true">
            <chunk reader="gameFileItemReader" writer="gameWriter"
                   commit-interval="10"/>
        </tasklet>
    </step>
    <step id="playerSummarization">
        <tasklet start-limit="2">
            <chunk reader="playerSummarizationSource" writer="summaryWriter"
                   commit-interval="10"/>
        </tasklet>
    </step>
</job>
```

下面的 Java 示例展示了如何将作业配置为具有可以重新启动的步骤:

Java 配置

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

@Bean
public Step playerLoad() {
	return this.stepBuilderFactory.get("playerLoad")
			.<String, String>chunk(10)
			.reader(playerFileItemReader())
			.writer(playerWriter())
			.build();
}

@Bean
public Step gameLoad() {
	return this.stepBuilderFactory.get("gameLoad")
			.allowStartIfComplete(true)
			.<String, String>chunk(10)
			.reader(gameFileItemReader())
			.writer(gameWriter())
			.build();
}

@Bean
public Step playerSummarization() {
	return this.stepBuilderFactory.get("playerSummarization")
			.startLimit(2)
			.<String, String>chunk(10)
			.reader(playerSummarizationSource())
			.writer(summaryWriter())
			.build();
}
```

前面的示例配置用于加载有关足球比赛的信息并对其进行总结的作业。它包含三个步骤:`playerLoad``gameLoad``playerSummarization``playerLoad`步骤从平面文件加载玩家信息,而`gameLoad`步骤对游戏也是如此。最后一步,`playerSummarization`,然后根据提供的游戏总结每个玩家的统计数据。假设`playerLoad`加载的文件必须只加载一次,但是`gameLoad`可以加载特定目录中的任何游戏,并在它们成功加载到数据库后将其删除。因此,`playerLoad`步骤不包含额外的配置。它可以启动任意次数,如果完成,则跳过。但是,每次都需要运行`gameLoad`步骤,以防自上次运行以来添加了额外的文件。它有“允许-启动-如果-完成”设置为“真”,以便始终被启动。(假设游戏加载到的数据库表上有一个进程指示器,以确保新的游戏可以通过摘要步骤正确地找到)。摘要步骤是作业中最重要的一步,它的起始限制为 2。这是有用的,因为如果该步骤持续失败,新的退出代码将返回给控制作业执行的操作符,并且在手动干预发生之前,它不能再次启动。

|   |此作业为该文档提供了一个示例,它与示例项目中的`footballJob`不同。|
|---|--------------------------------------------------------------------------------------------------------------------|

本节的其余部分描述了`footballJob`示例的三次运行中的每一次运行的情况。

运行 1:

1. `playerLoad`运行并成功完成,将 400 名玩家添加到“玩家”表中。

2. `gameLoad`运行和处理 11 个游戏数据文件,并将其内容加载到“游戏”表中。

3. `playerSummarization`开始处理,5 分钟后失败。

运行 2:

1. `playerLoad`不运行,因为它已经成功地完成了,并且`allow-start-if-complete`是’false’(默认值)。

2. `gameLoad`再次运行并处理另外 2 个文件,并将其内容加载到“Games”表中(进程指示器指示它们尚未被处理)。

3. `playerSummarization`开始处理所有剩余的游戏数据(使用进程指示器进行过滤),并在 30 分钟后再次失败。

运行 3:

1. `playerLoad`不运行,因为它已经成功地完成了,并且`allow-start-if-complete`是’false’(默认值)。

2. `gameLoad`再次运行并处理另外 2 个文件,并将其内容加载到“Games”表中(进程指示器指示它们尚未被处理)。

3. 由于这是`playerSummarization`的第三次执行,因此`playerSummarization`未启动并立即终止作业,并且其限制仅为 2。要么必须提高限制,要么必须执行`Job`作为新的`JobInstance`

茶陵後's avatar
茶陵後 已提交
421
#### 配置跳过逻辑
dallascao's avatar
dallascao 已提交
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508

在许多情况下,在处理过程中遇到的错误不会导致`Step`失败,而是应该跳过。这通常是一个必须由了解数据本身及其含义的人做出的决定。例如,财务数据可能不会被跳过,因为它会导致资金转移,而这需要完全准确。另一方面,加载供应商列表可能会允许跳过。如果某个供应商由于格式化不正确或缺少必要的信息而未加载,那么很可能就不存在问题。通常,这些不良记录也会被记录下来,稍后在讨论听众时会对此进行讨论。

下面的 XML 示例展示了使用跳过限制的示例:

XML 配置

```
<step id="step1">
   <tasklet>
      <chunk reader="flatFileItemReader" writer="itemWriter"
             commit-interval="10" skip-limit="10">
         <skippable-exception-classes>
            <include class="org.springframework.batch.item.file.FlatFileParseException"/>
         </skippable-exception-classes>
      </chunk>
   </tasklet>
</step>
```

下面的 Java 示例展示了一个使用跳过限制的示例:

Java 配置

```
@Bean
public Step step1() {
	return this.stepBuilderFactory.get("step1")
				.<String, String>chunk(10)
				.reader(flatFileItemReader())
				.writer(itemWriter())
				.faultTolerant()
				.skipLimit(10)
				.skip(FlatFileParseException.class)
				.build();
}
```

在前面的示例中,使用了`FlatFileItemReader`。如果在任何时候抛出一个`FlatFileParseException`,则跳过该项并将其计入总跳过限制 10。声明的异常(及其子类)可能会在块处理的任何阶段(读、进程、写)抛出,但是在步骤执行中,读、进程和写的跳过是单独的计数,但是该限制适用于所有跳过。一旦达到跳过限制,发现的下一个异常将导致该步骤失败。换句话说,第 11 跳会触发异常,而不是第 10 跳会触发异常。

上述示例的一个问题是,除了`FlatFileParseException`之外的任何其他异常都会导致`Job`失败。在某些情况下,这可能是正确的行为。然而,在其他情况下,可能更容易确定哪些异常应该导致失败,并跳过其他所有情况。

下面的 XML 示例展示了一个排除特定异常的示例:

XML 配置

```
<step id="step1">
    <tasklet>
        <chunk reader="flatFileItemReader" writer="itemWriter"
               commit-interval="10" skip-limit="10">
            <skippable-exception-classes>
                <include class="java.lang.Exception"/>
                <exclude class="java.io.FileNotFoundException"/>
            </skippable-exception-classes>
        </chunk>
    </tasklet>
</step>
```

下面的 Java 示例展示了一个排除特定异常的示例:

Java 配置

```
@Bean
public Step step1() {
	return this.stepBuilderFactory.get("step1")
				.<String, String>chunk(10)
				.reader(flatFileItemReader())
				.writer(itemWriter())
				.faultTolerant()
				.skipLimit(10)
				.skip(Exception.class)
				.noSkip(FileNotFoundException.class)
				.build();
}
```

通过将`java.lang.Exception`标识为可跳过的异常类,配置指示所有`Exceptions`都是可跳过的。但是,通过“排除”`java.io.FileNotFoundException`,该配置将可跳过的异常类的列表细化为所有`Exceptions`*除了*`FileNotFoundException`。任何被排除的异常类如果遇到都是致命的(也就是说,它们不会被跳过)。

对于遇到的任何异常,可跳跃性由类层次结构中最近的超类决定。任何未分类的例外情况都被视为“致命的”。

`<include/>``<exclude/>`元素的顺序并不重要。

`skip``noSkip`方法调用的顺序并不重要。

茶陵後's avatar
茶陵後 已提交
509
#### 配置重试逻辑
dallascao's avatar
dallascao 已提交
510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545

在大多数情况下,你希望异常导致跳过或`Step`失败。然而,并非所有的例外都是确定性的。如果在读取时遇到`FlatFileParseException`,则总是为该记录抛出该记录。重置`ItemReader`不会有帮助。但是,对于其他异常,例如`DeadlockLoserDataAccessException`,它表示当前进程试图更新另一个进程持有锁定的记录。等待并再次尝试可能会取得成功。

在 XML 中,重试应该配置如下:

```
<step id="step1">
   <tasklet>
      <chunk reader="itemReader" writer="itemWriter"
             commit-interval="2" retry-limit="3">
         <retryable-exception-classes>
            <include class="org.springframework.dao.DeadlockLoserDataAccessException"/>
         </retryable-exception-classes>
      </chunk>
   </tasklet>
</step>
```

在 Java 中,重试应该配置如下:

```
@Bean
public Step step1() {
	return this.stepBuilderFactory.get("step1")
				.<String, String>chunk(2)
				.reader(itemReader())
				.writer(itemWriter())
				.faultTolerant()
				.retryLimit(3)
				.retry(DeadlockLoserDataAccessException.class)
				.build();
}
```

`Step`允许对单个项目的重试次数进行限制,并提供“可重试”的异常列表。有关重试工作原理的更多详细信息,请参见[retry](retry.html#retry)

茶陵後's avatar
茶陵後 已提交
546
#### 控制回滚
dallascao's avatar
dallascao 已提交
547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581

默认情况下,不管是重试还是跳过,从`ItemWriter`抛出的任何异常都会导致由`Step`控制的事务回滚。如果按照前面描述的方式配置了 Skip,则从`ItemReader`抛出的异常不会导致回滚。但是,在许多情况下,从`ItemWriter`抛出的异常不应该导致回滚,因为没有发生任何使事务无效的操作。出于这个原因,`Step`可以配置一个不应导致回滚的异常列表。

在 XML 中,你可以按以下方式控制回滚:

XML 配置

```
<step id="step1">
   <tasklet>
      <chunk reader="itemReader" writer="itemWriter" commit-interval="2"/>
      <no-rollback-exception-classes>
         <include class="org.springframework.batch.item.validator.ValidationException"/>
      </no-rollback-exception-classes>
   </tasklet>
</step>
```

在 Java 中,你可以按以下方式控制回滚:

Java 配置

```
@Bean
public Step step1() {
	return this.stepBuilderFactory.get("step1")
				.<String, String>chunk(2)
				.reader(itemReader())
				.writer(itemWriter())
				.faultTolerant()
				.noRollback(ValidationException.class)
				.build();
}
```

茶陵後's avatar
茶陵後 已提交
582
##### 事务读取器
dallascao's avatar
dallascao 已提交
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614

`ItemReader`的基本契约是,它只是远期的。该步骤缓冲读写器的输入,以便在回滚的情况下,不需要从读写器重新读取项目。然而,在某些情况下,读取器是建立在事务性资源之上的,例如 JMS 队列。在这种情况下,由于队列与回滚的事务绑定在一起,因此从队列中拉出的消息将被放回。出于这个原因,可以将该步骤配置为不缓冲项。

下面的示例展示了如何创建不使用 XML 缓冲项的读取器:

XML 配置

```
<step id="step1">
    <tasklet>
        <chunk reader="itemReader" writer="itemWriter" commit-interval="2"
               is-reader-transactional-queue="true"/>
    </tasklet>
</step>
```

下面的示例展示了如何创建不在 Java 中缓冲项的读取器:

Java 配置

```
@Bean
public Step step1() {
	return this.stepBuilderFactory.get("step1")
				.<String, String>chunk(2)
				.reader(itemReader())
				.writer(itemWriter())
				.readerIsTransactionalQueue()
				.build();
}
```

茶陵後's avatar
茶陵後 已提交
615
#### 事务属性
dallascao's avatar
dallascao 已提交
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654

事务属性可用于控制`isolation``propagation``timeout`设置。有关设置事务属性的更多信息,请参见[Spring core documentation](https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction)

以下示例在 XML 中设置`isolation``propagation``timeout`事务属性:

XML 配置

```
<step id="step1">
    <tasklet>
        <chunk reader="itemReader" writer="itemWriter" commit-interval="2"/>
        <transaction-attributes isolation="DEFAULT"
                                propagation="REQUIRED"
                                timeout="30"/>
    </tasklet>
</step>
```

下面的示例在 Java 中设置`isolation``propagation``timeout`事务属性:

Java 配置

```
@Bean
public Step step1() {
	DefaultTransactionAttribute attribute = new DefaultTransactionAttribute();
	attribute.setPropagationBehavior(Propagation.REQUIRED.value());
	attribute.setIsolationLevel(Isolation.DEFAULT.value());
	attribute.setTimeout(30);

	return this.stepBuilderFactory.get("step1")
				.<String, String>chunk(2)
				.reader(itemReader())
				.writer(itemWriter())
				.transactionAttribute(attribute)
				.build();
}
```

茶陵後's avatar
茶陵後 已提交
655
#### 用`Step`注册`ItemStream`
dallascao's avatar
dallascao 已提交
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723

该步骤必须在其生命周期中的必要点处理`ItemStream`回调(有关`ItemStream`接口的更多信息,请参见[ItemStream](readersAndWriters.html#itemStream))。如果一个步骤失败并且可能需要重新启动,这是至关重要的,因为`ItemStream`接口是该步骤获取所需的关于两次执行之间的持久状态的信息的地方。

如果`ItemReader``ItemProcessor``ItemWriter`本身实现了`ItemStream`接口,那么这些接口将被自动注册。任何其他流都需要单独注册。在将委托等间接依赖注入到 Reader 和 Writer 中时,通常会出现这种情况。可以通过“流”元素在`step`上注册流。

下面的示例显示了如何在 XML 中的`step`上注册`stream`:

XML 配置

```
<step id="step1">
    <tasklet>
        <chunk reader="itemReader" writer="compositeWriter" commit-interval="2">
            <streams>
                <stream ref="fileItemWriter1"/>
                <stream ref="fileItemWriter2"/>
            </streams>
        </chunk>
    </tasklet>
</step>

<beans:bean id="compositeWriter"
            class="org.springframework.batch.item.support.CompositeItemWriter">
    <beans:property name="delegates">
        <beans:list>
            <beans:ref bean="fileItemWriter1" />
            <beans:ref bean="fileItemWriter2" />
        </beans:list>
    </beans:property>
</beans:bean>
```

下面的示例展示了如何在 Java 中的`step`上注册`stream`:

Java 配置

```
@Bean
public Step step1() {
	return this.stepBuilderFactory.get("step1")
				.<String, String>chunk(2)
				.reader(itemReader())
				.writer(compositeItemWriter())
				.stream(fileItemWriter1())
				.stream(fileItemWriter2())
				.build();
}

/**
 * In Spring Batch 4, the CompositeItemWriter implements ItemStream so this isn't
 * necessary, but used for an example.
 */
@Bean
public CompositeItemWriter compositeItemWriter() {
	List<ItemWriter> writers = new ArrayList<>(2);
	writers.add(fileItemWriter1());
	writers.add(fileItemWriter2());

	CompositeItemWriter itemWriter = new CompositeItemWriter();

	itemWriter.setDelegates(writers);

	return itemWriter;
}
```

在上面的示例中,`CompositeItemWriter`不是`ItemStream`,但它的两个委托都是。因此,为了使框架能够正确地处理这两个委托编写器,必须将这两个委托编写器显式地注册为流。`ItemReader`不需要显式地注册为流,因为它是`Step`的直接属性。该步骤现在可以重新启动,并且在发生故障时,Reader 和 Writer 的状态被正确地持久化。

茶陵後's avatar
茶陵後 已提交
724
#### 拦截`Step`执行
dallascao's avatar
dallascao 已提交
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764

就像`Job`一样,在执行`Step`的过程中有许多事件,其中用户可能需要执行某些功能。例如,为了写出到需要页脚的平面文件,需要在`ItemWriter`已完成时通知`Step`,以便可以写出页脚。这可以通过使用许多`Step`范围的侦听器中的一个来实现。

实现`StepListener`扩展之一的任何类(但不包括接口本身,因为它是空的)都可以通过`listeners`元素应用到一个步骤。`listeners`元素在步骤、任务 let 或块声明中是有效的。建议你在其函数应用的级别上声明侦听器,或者,如果它是多功能的(例如`StepExecutionListener``ItemReadListener`),则在其应用的最细粒度级别上声明它。

下面的示例展示了一个应用于 XML 块级别的侦听器:

XML 配置

```
<step id="step1">
    <tasklet>
        <chunk reader="reader" writer="writer" commit-interval="10"/>
        <listeners>
            <listener ref="chunkListener"/>
        </listeners>
    </tasklet>
</step>
```

下面的示例展示了在 Java 中应用于块级别的侦听器:

Java 配置

```
@Bean
public Step step1() {
	return this.stepBuilderFactory.get("step1")
				.<String, String>chunk(10)
				.reader(reader())
				.writer(writer())
				.listener(chunkListener())
				.build();
}
```

一个`ItemReader``ItemWriter``ItemProcessor`本身实现了`StepListener`接口之一的`Step`如果使用名称空间`<step>`元素或`*StepFactoryBean`工厂之一,则自动在`Step`中注册。这仅适用于直接注入`Step`的组件。如果侦听器嵌套在另一个组件中,则需要显式地对其进行注册(如前面在[用`Step`注册`ItemStream`](#registeringitemStreams)中所述)。

除了`StepListener`接口外,还提供了注释来解决相同的问题。普通的旧 Java 对象可以具有带有这些注释的方法,然后将这些方法转换为相应的`StepListener`类型。对组块组件的定制实现进行注释也是常见的,例如`ItemReader``ItemWriter``Tasklet`。XML 解析器分析`<listener/>`元素的注释,并在构建器中使用`listener`方法注册注释,因此你所需要做的就是使用 XML 名称空间或构建器通过一个步骤注册侦听器。

茶陵後's avatar
茶陵後 已提交
765
##### `StepExecutionListener`
dallascao's avatar
dallascao 已提交
766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786

`StepExecutionListener`表示用于`Step`执行的最通用的侦听器。它允许在`Step`开始之前和结束之后发出通知,无论是正常结束还是失败,如下例所示:

```
public interface StepExecutionListener extends StepListener {

    void beforeStep(StepExecution stepExecution);

    ExitStatus afterStep(StepExecution stepExecution);

}
```

`ExitStatus``afterStep`的返回类型,以便使侦听器有机会修改在完成`Step`时返回的退出代码。

与此接口对应的注释是:

* `@BeforeStep`

* `@AfterStep`

茶陵後's avatar
茶陵後 已提交
787
##### `ChunkListener`
dallascao's avatar
dallascao 已提交
788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812

块被定义为在事务范围内处理的项。在每个提交间隔时间提交一个事务,提交一个“块”。a`ChunkListener`可用于在块开始处理之前或在块成功完成之后执行逻辑,如以下接口定义所示:

```
public interface ChunkListener extends StepListener {

    void beforeChunk(ChunkContext context);
    void afterChunk(ChunkContext context);
    void afterChunkError(ChunkContext context);

}
```

在事务启动后但在`ItemReader`上调用读之前调用 BeforeChunk 方法。相反,`afterChunk`是在提交了块之后调用的(如果有回滚,则根本不调用)。

与此接口对应的注释是:

* `@BeforeChunk`

* `@AfterChunk`

* `@AfterChunkError`

当没有块声明时,可以应用`ChunkListener``TaskletStep`负责调用`ChunkListener`,因此它也适用于非面向项目的任务小程序(在任务小程序之前和之后调用它)。

茶陵後's avatar
茶陵後 已提交
813
##### `ItemReadListener`
dallascao's avatar
dallascao 已提交
814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836

在前面讨论跳过逻辑时,提到了记录跳过的记录可能是有益的,这样可以在以后处理它们。在读取错误的情况下,可以使用`ItemReaderListener`完成此操作,如下面的接口定义所示:

```
public interface ItemReadListener<T> extends StepListener {

    void beforeRead();
    void afterRead(T item);
    void onReadError(Exception ex);

}
```

在每次调用之前调用`beforeRead`方法来读取`ItemReader`。在每次成功调用 read 之后,都会调用`afterRead`方法,并传递被读取的项。如果在读取时出现错误,则调用`onReadError`方法。提供了所遇到的异常,以便可以对其进行记录。

与此接口对应的注释是:

* `@BeforeRead`

* `@AfterRead`

* `@OnReadError`

茶陵後's avatar
茶陵後 已提交
837
##### `ItemProcessListener`
dallascao's avatar
dallascao 已提交
838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860

就像`ItemReadListener`一样,可以“监听”项目的处理,如以下接口定义所示:

```
public interface ItemProcessListener<T, S> extends StepListener {

    void beforeProcess(T item);
    void afterProcess(T item, S result);
    void onProcessError(T item, Exception e);

}
```

`ItemProcessor`上,在`process`之前调用`beforeProcess`方法,并将其交给要处理的项。在成功处理该项后,将调用`afterProcess`方法。如果在处理过程中出现错误,则调用`onProcessError`方法。提供了遇到的异常和试图处理的项,以便可以对它们进行记录。

与此接口对应的注释是:

* `@BeforeProcess`

* `@AfterProcess`

* `@OnProcessError`

茶陵後's avatar
茶陵後 已提交
861
##### `ItemWriteListener`
dallascao's avatar
dallascao 已提交
862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884

可以使用`ItemWriteListener`“监听”项目的写入,如以下接口定义所示:

```
public interface ItemWriteListener<S> extends StepListener {

    void beforeWrite(List<? extends S> items);
    void afterWrite(List<? extends S> items);
    void onWriteError(Exception exception, List<? extends S> items);

}
```

`ItemWriter`上的`write`之前调用`beforeWrite`方法,并将所写的项列表交给该方法。在成功地写入项目之后,将调用`afterWrite`方法。如果写入时出现错误,则调用`onWriteError`方法。提供了遇到的异常和试图写入的项,以便可以对它们进行记录。

与此接口对应的注释是:

* `@BeforeWrite`

* `@AfterWrite`

* `@OnWriteError`

茶陵後's avatar
茶陵後 已提交
885
##### `SkipListener`
dallascao's avatar
dallascao 已提交
886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908

`ItemReadListener``ItemProcessListener``ItemWriteListener`都提供了通知错误的机制,但没有一个通知你记录实际上已被跳过。例如,`onWriteError`即使一个项目被重试并成功,也会被调用。出于这个原因,有一个单独的接口用于跟踪跳过的项目,如以下接口定义所示:

```
public interface SkipListener<T,S> extends StepListener {

    void onSkipInRead(Throwable t);
    void onSkipInProcess(T item, Throwable t);
    void onSkipInWrite(S item, Throwable t);

}
```

`onSkipInRead`是在读取时跳过项时调用的。需要注意的是,回滚可能会导致同一项被多次注册为跳过一次。`onSkipInWrite`是在写入时跳过一项时调用的。因为该项已被成功读取(而不是跳过),所以还将该项本身作为参数提供给它。

与此接口对应的注释是:

* `@OnSkipInRead`

* `@OnSkipInWrite`

* `@OnSkipInProcess`

茶陵後's avatar
茶陵後 已提交
909
###### 跳过侦听器和事务
dallascao's avatar
dallascao 已提交
910 911 912 913 914 915 916

`SkipListener`最常见的用例之一是注销一个跳过的项,这样就可以使用另一个批处理过程甚至人工过程来评估和修复导致跳过的问题。因为在许多情况下原始事务可能会被回滚, Spring Batch 提供了两个保证:

1. 每个项目只调用一次适当的 Skip 方法(取决于错误发生的时间)。

2. 总是在事务提交之前调用`SkipListener`。这是为了确保侦听器调用的任何事务资源不会因`ItemWriter`中的故障而回滚。

茶陵後's avatar
茶陵後 已提交
917
### `TaskletStep`
dallascao's avatar
dallascao 已提交
918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944

[面向块的处理](#chunkOrientedProcessing)并不是在`Step`中进行处理的唯一方法。如果`Step`必须包含一个简单的存储过程调用怎么办?你可以将调用实现为`ItemReader`,并在过程完成后返回 null。然而,这样做有点不自然,因为需要有一个 no-op`ItemWriter`。 Spring Batch 为此场景提供了`TaskletStep`

`Tasklet`是一个简单的接口,它有一个方法`execute`,它被`TaskletStep`反复调用,直到它返回`RepeatStatus.FINISHED`或抛出异常来表示失败。对`Tasklet`的每个调用都包装在一个事务中。`Tasklet`实现器可以调用一个存储过程、一个脚本或一个简单的 SQL 更新语句。

要在 XML 中创建`TaskletStep``<tasklet/>`元素的’ref’属性应该引用定义`Tasklet`对象的 Bean。在`<tasklet/>`中不应该使用`<chunk/>`元素。下面的示例展示了一个简单的任务:

```
<step id="step1">
    <tasklet ref="myTasklet"/>
</step>
```

要在 Java 中创建`TaskletStep`,传递给构建器的`tasklet`方法的 Bean 应该实现`Tasklet`接口。在构建`TaskletStep`时,不应调用`chunk`。下面的示例展示了一个简单的任务:

```
@Bean
public Step step1() {
    return this.stepBuilderFactory.get("step1")
    			.tasklet(myTasklet())
    			.build();
}
```

|   |`TaskletStep`如果实现`StepListener`接口,则自动将<br/>任务集注册为`StepListener`。|
|---|-----------------------------------------------------------------------------------------------------------------------|

茶陵後's avatar
茶陵後 已提交
945
#### `TaskletAdapter`
dallascao's avatar
dallascao 已提交
946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977

`ItemReader``ItemWriter`接口的其他适配器一样,`Tasklet`接口包含一个允许自适应到任何预先存在的类的实现:`TaskletAdapter`。这可能有用的一个例子是现有的 DAO,该 DAO 用于更新一组记录上的标志。`TaskletAdapter`可以用来调用这个类,而不必为`Tasklet`接口编写适配器。

下面的示例展示了如何在 XML 中定义`TaskletAdapter`:

XML 配置

```
<bean id="myTasklet" class="o.s.b.core.step.tasklet.MethodInvokingTaskletAdapter">
    <property name="targetObject">
        <bean class="org.mycompany.FooDao"/>
    </property>
    <property name="targetMethod" value="updateFoo" />
</bean>
```

下面的示例展示了如何在 Java 中定义`TaskletAdapter`:

Java 配置

```
@Bean
public MethodInvokingTaskletAdapter myTasklet() {
	MethodInvokingTaskletAdapter adapter = new MethodInvokingTaskletAdapter();

	adapter.setTargetObject(fooDao());
	adapter.setTargetMethod("updateFoo");

	return adapter;
}
```

茶陵後's avatar
茶陵後 已提交
978
#### 示例`Tasklet`实现
dallascao's avatar
dallascao 已提交
979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065

许多批处理作业包含一些步骤,这些步骤必须在主处理开始之前完成,以便设置各种资源,或者在处理完成之后清理这些资源。如果作业中的文件很多,那么在成功地将某些文件上传到另一个位置后,通常需要在本地删除这些文件。下面的示例(取自[Spring Batch samples project](https://github.com/spring-projects/spring-batch/tree/master/spring-batch-samples))是一个带有这样的职责的`Tasklet`实现:

```
public class FileDeletingTasklet implements Tasklet, InitializingBean {

    private Resource directory;

    public RepeatStatus execute(StepContribution contribution,
                                ChunkContext chunkContext) throws Exception {
        File dir = directory.getFile();
        Assert.state(dir.isDirectory());

        File[] files = dir.listFiles();
        for (int i = 0; i < files.length; i++) {
            boolean deleted = files[i].delete();
            if (!deleted) {
                throw new UnexpectedJobExecutionException("Could not delete file " +
                                                          files[i].getPath());
            }
        }
        return RepeatStatus.FINISHED;
    }

    public void setDirectoryResource(Resource directory) {
        this.directory = directory;
    }

    public void afterPropertiesSet() throws Exception {
        Assert.notNull(directory, "directory must be set");
    }
}
```

前面的`tasklet`实现将删除给定目录中的所有文件。需要注意的是,`execute`方法只被调用一次。剩下的就是引用来自`step``tasklet`

下面的示例展示了如何在 XML 中引用来自`step``tasklet`:

XML 配置

```
<job id="taskletJob">
    <step id="deleteFilesInDir">
       <tasklet ref="fileDeletingTasklet"/>
    </step>
</job>

<beans:bean id="fileDeletingTasklet"
            class="org.springframework.batch.sample.tasklet.FileDeletingTasklet">
    <beans:property name="directoryResource">
        <beans:bean id="directory"
                    class="org.springframework.core.io.FileSystemResource">
            <beans:constructor-arg value="target/test-outputs/test-dir" />
        </beans:bean>
    </beans:property>
</beans:bean>
```

下面的示例展示了如何在 Java 中引用来自`step``tasklet`:

Java 配置

```
@Bean
public Job taskletJob() {
	return this.jobBuilderFactory.get("taskletJob")
				.start(deleteFilesInDir())
				.build();
}

@Bean
public Step deleteFilesInDir() {
	return this.stepBuilderFactory.get("deleteFilesInDir")
				.tasklet(fileDeletingTasklet())
				.build();
}

@Bean
public FileDeletingTasklet fileDeletingTasklet() {
	FileDeletingTasklet tasklet = new FileDeletingTasklet();

	tasklet.setDirectoryResource(new FileSystemResource("target/test-outputs/test-dir"));

	return tasklet;
}
```

茶陵後's avatar
茶陵後 已提交
1066
### 控制阶跃流
dallascao's avatar
dallascao 已提交
1067 1068 1069

在拥有一份工作的过程中,有了将步骤组合在一起的能力,就需要能够控制工作如何从一个步骤“流动”到另一个步骤。a`Step`失败并不一定意味着`Job`应该失败。此外,可能有不止一种类型的“成功”来决定下一步应该执行哪个`Step`。根据`Steps`组的配置方式,某些步骤甚至可能根本不会被处理。

茶陵後's avatar
茶陵後 已提交
1070
#### 序贯流
dallascao's avatar
dallascao 已提交
1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111

最简单的流程场景是所有步骤都按顺序执行的作业,如下图所示:

![顺序流动](./images/sequential-flow.png)

图 4。顺序流动

这可以通过使用`step`中的“next”来实现。

下面的示例展示了如何在 XML 中使用`next`属性:

XML 配置

```
<job id="job">
    <step id="stepA" parent="s1" next="stepB" />
    <step id="stepB" parent="s2" next="stepC"/>
    <step id="stepC" parent="s3" />
</job>
```

下面的示例展示了如何在 Java 中使用`next()`方法:

Java 配置

```
@Bean
public Job job() {
	return this.jobBuilderFactory.get("job")
				.start(stepA())
				.next(stepB())
				.next(stepC())
				.build();
}
```

在上面的场景中,“步骤 A”首先运行,因为它是列出的第一个`Step`。如果“步骤 A”正常完成,那么“步骤 B”运行,依此类推。但是,如果“步骤 A”失败,则整个`Job`失败,并且“步骤 B”不执行。

|   |对于 Spring 批处理 XML 命名空间,配置中列出的第一步是 *always*`Job`运行的第一步。其他步骤元素的顺序并不是<br/>重要的,但是第一步必须始终首先出现在 XML 中。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

茶陵後's avatar
茶陵後 已提交
1112
#### 条件流
dallascao's avatar
dallascao 已提交
1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172

在上面的例子中,只有两种可能性:

1. `step`成功,应该执行下一个`step`

2. `step`失败,因此,`job`应该失败。

在许多情况下,这可能就足够了。但是,如果`step`的失败应该触发不同的`step`,而不是导致失败,那么在这种情况下该怎么办?下图显示了这样的流程:

![条件流](./images/conditional-flow.png)

图 5。条件流

为了处理更复杂的场景, Spring 批 XML 命名空间允许在 Step 元素中定义转换元素。一个这样的转换是`next`元素。与`next`属性类似,`next`元素告诉`Job`下一个执行的是`Step`。然而,与属性不同的是,在给定的`Step`上允许任意数量的`next`元素,并且在失败的情况下没有默认行为。这意味着,如果使用了转换元素,则必须显式地定义`Step`转换的所有行为。还请注意,单个步骤不能同时具有`next`属性和`transition`元素。

`next`元素指定要匹配的模式和接下来要执行的步骤,如以下示例所示:

XML 配置

```
<job id="job">
    <step id="stepA" parent="s1">
        <next on="*" to="stepB" />
        <next on="FAILED" to="stepC" />
    </step>
    <step id="stepB" parent="s2" next="stepC" />
    <step id="stepC" parent="s3" />
</job>
```

Java API 提供了一组流畅的方法,允许你指定流程以及当步骤失败时要做什么。下面的示例显示了如何指定一个步骤(`stepA`),然后继续执行两个不同步骤中的任何一个(`stepB``stepC`),这取决于`stepA`是否成功:

Java 配置

```
@Bean
public Job job() {
	return this.jobBuilderFactory.get("job")
				.start(stepA())
				.on("*").to(stepB())
				.from(stepA()).on("FAILED").to(stepC())
				.end()
				.build();
}
```

当使用 XML 配置时,转换元素的`on`属性使用一个简单的模式匹配方案来匹配执行`ExitStatus`所产生的`ExitStatus`

当使用 Java 配置时,`on()`方法使用一个简单的模式匹配方案来匹配执行`ExitStatus`所产生的`ExitStatus`

模式中只允许使用两个特殊字符:

*\*”匹配零个或多个字符

* “?”正好与一个字符匹配。

例如,“C\*t”匹配“cat”和“count”,而“C?t”匹配“cat”,但不匹配“count”。

虽然对`Step`上的转换元素的数量没有限制,但是如果`Step`执行导致元素不覆盖的`ExitStatus`,那么框架将抛出一个异常,而`Job`将失败。该框架自动命令从最特定到最不特定的转换。这意味着,即使在上面的示例中将顺序交换为“stepa”,`ExitStatus`的“failed”仍将转到“stepc”。

茶陵後's avatar
茶陵後 已提交
1173
##### 批处理状态与退出状态
dallascao's avatar
dallascao 已提交
1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254

在为条件流配置`Job`时,重要的是要理解`BatchStatus``ExitStatus`之间的区别。`BatchStatus`是一个枚举,它是`JobExecution``StepExecution`的属性,框架使用它来记录`Job``Step`的状态。它可以是以下值之一:`COMPLETED``STARTING``STARTED``STOPPING``STOPPED``FAILED``ABANDONED`,或`UNKNOWN`。其中大多数是不言自明的:`COMPLETED`是当一个步骤或作业成功完成时设置的状态,`FAILED`是当它失败时设置的状态,依此类推。

使用 XML 配置时,下面的示例包含“next”元素:

```
<next on="FAILED" to="stepB" />
```

使用 Java 配置时,下面的示例包含“on”元素:

```
...
.from(stepA()).on("FAILED").to(stepB())
...
```

乍一看,“on”似乎引用了它所属的`Step``BatchStatus`。然而,它实际上引用了`ExitStatus``Step`。顾名思义,`ExitStatus`表示一个`Step`在完成执行后的状态。

更具体地说,当使用 XML 配置时,前面的 XML 配置示例中显示的“next”元素引用`ExitStatus`的退出代码。

当使用 Java 配置时,前面的 Java 配置示例中显示的“on()”方法引用`ExitStatus`的退出代码。

在英语中,它写着:“如果退出代码是`FAILED`,则转到 STEPB。”默认情况下,对于`Step`,退出代码始终与`BatchStatus`相同,这就是上面的条目有效的原因。但是,如果退出代码需要不同,该怎么办?一个很好的例子来自于 Samples 项目中的 Skip Sample 作业:

下面的示例展示了如何使用 XML 中的不同退出代码:

XML 配置

```
<step id="step1" parent="s1">
    <end on="FAILED" />
    <next on="COMPLETED WITH SKIPS" to="errorPrint1" />
    <next on="*" to="step2" />
</step>
```

下面的示例展示了如何使用 Java 中的不同退出代码:

Java 配置

```
@Bean
public Job job() {
	return this.jobBuilderFactory.get("job")
			.start(step1()).on("FAILED").end()
			.from(step1()).on("COMPLETED WITH SKIPS").to(errorPrint1())
			.from(step1()).on("*").to(step2())
			.end()
			.build();
}
```

`step1`有三种可能性:

1. `Step`失败,在这种情况下,作业应该失败。

2. `Step`成功完成。

3. `Step`已成功完成,但退出代码为“与跳过一起完成”。在这种情况下,应该运行一个不同的步骤来处理错误。

前面的配置可以正常工作。但是,需要根据跳过记录的执行情况更改退出代码,如以下示例所示:

```
public class SkipCheckingListener extends StepExecutionListenerSupport {
    public ExitStatus afterStep(StepExecution stepExecution) {
        String exitCode = stepExecution.getExitStatus().getExitCode();
        if (!exitCode.equals(ExitStatus.FAILED.getExitCode()) &&
              stepExecution.getSkipCount() > 0) {
            return new ExitStatus("COMPLETED WITH SKIPS");
        }
        else {
            return null;
        }
    }
}
```

上面的代码是`StepExecutionListener`,该代码首先检查以确保`Step`成功,然后检查`StepExecution`上的跳过计数是否高于
0. 如果这两个条件都满足,则返回一个新的`ExitStatus`,其退出代码为`COMPLETED WITH SKIPS`

茶陵後's avatar
茶陵後 已提交
1255
#### 配置停止
dallascao's avatar
dallascao 已提交
1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285

在讨论了[batchstatus 和 exitstatus](#batchStatusVsExitStatus)之后,人们可能想知道如何确定`BatchStatus``ExitStatus``Job`。虽然这些状态是由执行的代码为`Step`确定的,但`Job`的状态是基于配置确定的。

到目前为止,讨论的所有作业配置都至少有一个没有转换的最终`Step`

在下面的 XML 示例中,在`step`执行之后,`Job`结束:

```
<step id="stepC" parent="s3"/>
```

在下面的 Java 示例中,在`step`执行之后,`Job`结束:

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

如果没有为`Step`定义转换,则`Job`的状态定义如下:

* 如果`Step``ExitStatus`结尾失败,则`BatchStatus``ExitStatus`中的`Job`都是`FAILED`

* 否则,`BatchStatus``ExitStatus``Job`都是`COMPLETED`

虽然这种终止批处理作业的方法对于某些批处理作业(例如简单的连续步骤作业)来说已经足够了,但可能需要自定义的作业停止场景。为此, Spring Batch 提供了三个转换元素来停止`Job`(除了我们前面讨论的[`next`元素](#NextElement))。这些停止元素中的每一个都以特定的`BatchStatus`停止`Job`。重要的是要注意,停止转换元件对`BatchStatus`中的任何`Steps``ExitStatus``ExitStatus`都没有影响。这些元素只影响`Job`的最终状态。例如,对于作业中的每一步,都可能具有`FAILED`的状态,但是对于作业,则可能具有`COMPLETED`的状态。

茶陵後's avatar
茶陵後 已提交
1286
##### 以一步结尾
dallascao's avatar
dallascao 已提交
1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323

配置步骤结束指示`Job``BatchStatus``COMPLETED`停止。已经完成了 status`COMPLETED``Job`不能重新启动(框架抛出一个`JobInstanceAlreadyCompleteException`)。

在使用 XML 配置时,此任务使用“end”元素。`end`元素还允许一个可选的’exit-code’属性,该属性可用于自定义`Job``ExitStatus`。如果没有给出“exit-code”属性,则`ExitStatus`默认为`COMPLETED`,以匹配`BatchStatus`

当使用 Java 配置时,此任务使用“end”方法。`end`方法还允许一个可选的’exitstatus’参数,该参数可用于自定义`Job`中的`ExitStatus`。如果不提供“exitstatus”值,则`ExitStatus`默认为`COMPLETED`,以匹配`BatchStatus`

考虑以下场景:如果`step2`失败,则`Job`停止,`BatchStatus``COMPLETED``ExitStatus``COMPLETED``step3`不运行。否则,执行移动到`step3`。请注意,如果`step2`失败,则`Job`不可重启(因为状态是`COMPLETED`)。

下面的示例以 XML 形式展示了该场景:

```
<step id="step1" parent="s1" next="step2">

<step id="step2" parent="s2">
    <end on="FAILED"/>
    <next on="*" to="step3"/>
</step>

<step id="step3" parent="s3">
```

下面的示例展示了 Java 中的场景:

```
@Bean
public Job job() {
	return this.jobBuilderFactory.get("job")
				.start(step1())
				.next(step2())
				.on("FAILED").end()
				.from(step2()).on("*").to(step3())
				.end()
				.build();
}
```

茶陵後's avatar
茶陵後 已提交
1324
##### 失败的步骤
dallascao's avatar
dallascao 已提交
1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362

配置在给定点失败的步骤指示`Job``BatchStatus``FAILED`停止。与 END 不同的是,`Job`的失败并不会阻止`Job`被重新启动。

在使用 XML 配置时,“fail”元素还允许一个可选的“exit-code”属性,该属性可用于自定义`Job`中的`ExitStatus`。如果没有给出“exit-code”属性,则`ExitStatus`默认为`FAILED`,以匹配`BatchStatus`

考虑下面的场景,如果`step2`失败,则`Job`停止,`BatchStatus``FAILED``ExitStatus``EARLY TERMINATION``step3`不执行。否则,执行移动到`step3`。此外,如果`step2`失败,并且`Job`被重新启动,那么在`step2`上再次开始执行。

下面的示例以 XML 形式展示了该场景:

XML 配置

```
<step id="step1" parent="s1" next="step2">

<step id="step2" parent="s2">
    <fail on="FAILED" exit-code="EARLY TERMINATION"/>
    <next on="*" to="step3"/>
</step>

<step id="step3" parent="s3">
```

下面的示例展示了 Java 中的场景:

Java 配置

```
@Bean
public Job job() {
	return this.jobBuilderFactory.get("job")
			.start(step1())
			.next(step2()).on("FAILED").fail()
			.from(step2()).on("*").to(step3())
			.end()
			.build();
}
```

茶陵後's avatar
茶陵後 已提交
1363
##### 在给定的步骤停止作业
dallascao's avatar
dallascao 已提交
1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394

将作业配置为在特定的步骤停止,将指示`Job`使用`BatchStatus``STOPPED`停止作业。停止`Job`可以在处理中提供临时中断,以便操作员可以在重新启动`Job`之前采取一些操作。

在使用 XML 配置时,“stop”元素需要一个“restart”属性,该属性指定了重新启动作业时执行应该在哪里进行的步骤。

当使用 Java 配置时,`stopAndRestart`方法需要一个“restart”属性,该属性指定作业重新启动时执行应该在哪里进行的步骤。

考虑以下场景:如果`step1``COMPLETE`结束,那么作业将停止。一旦重新启动,执行就开始于`step2`

以下清单以 XML 形式展示了该场景:

```
<step id="step1" parent="s1">
    <stop on="COMPLETED" restart="step2"/>
</step>

<step id="step2" parent="s2"/>
```

下面的示例展示了 Java 中的场景:

```
@Bean
public Job job() {
	return this.jobBuilderFactory.get("job")
			.start(step1()).on("COMPLETED").stopAndRestart(step2())
			.end()
			.build();
}
```

茶陵後's avatar
茶陵後 已提交
1395
#### 程序化流程决策
dallascao's avatar
dallascao 已提交
1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449

在某些情况下,可能需要比`ExitStatus`更多的信息来决定下一步执行哪个步骤。在这种情况下,可以使用`JobExecutionDecider`来辅助决策,如以下示例所示:

```
public class MyDecider implements JobExecutionDecider {
    public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
        String status;
        if (someCondition()) {
            status = "FAILED";
        }
        else {
            status = "COMPLETED";
        }
        return new FlowExecutionStatus(status);
    }
}
```

在下面的示例作业配置中,`decision`指定了要使用的决策器以及所有转换:

XML 配置

```
<job id="job">
    <step id="step1" parent="s1" next="decision" />

    <decision id="decision" decider="decider">
        <next on="FAILED" to="step2" />
        <next on="COMPLETED" to="step3" />
    </decision>

    <step id="step2" parent="s2" next="step3"/>
    <step id="step3" parent="s3" />
</job>

<beans:bean id="decider" class="com.MyDecider"/>
```

在下面的示例中,在使用 Java 配置时,实现`JobExecutionDecider`的 Bean 被直接传递到`next`调用。

Java 配置

```
@Bean
public Job job() {
	return this.jobBuilderFactory.get("job")
			.start(step1())
			.next(decider()).on("FAILED").to(step2())
			.from(decider()).on("COMPLETED").to(step3())
			.end()
			.build();
}
```

茶陵後's avatar
茶陵後 已提交
1450
#### 拆分流
dallascao's avatar
dallascao 已提交
1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498

到目前为止描述的每个场景都涉及一个`Job`,它以线性方式一次执行一个步骤。 Spring 除了这种典型的样式之外,批处理还允许使用并行的流来配置作业。

XML 命名空间允许你使用“split”元素。正如下面的示例所示,“split”元素包含一个或多个“flow”元素,可以在其中定义整个单独的流。“拆分”元素还可以包含前面讨论过的任何转换元素,例如“next”属性或“next”、“end”或“fail”元素。

```
<split id="split1" next="step4">
    <flow>
        <step id="step1" parent="s1" next="step2"/>
        <step id="step2" parent="s2"/>
    </flow>
    <flow>
        <step id="step3" parent="s3"/>
    </flow>
</split>
<step id="step4" parent="s4"/>
```

基于 Java 的配置允许你通过提供的构建器配置分割。正如下面的示例所示,“split”元素包含一个或多个“flow”元素,可以在其中定义整个单独的流。“拆分”元素还可以包含前面讨论过的任何转换元素,例如“next”属性或“next”、“end”或“fail”元素。

```
@Bean
public Flow flow1() {
	return new FlowBuilder<SimpleFlow>("flow1")
			.start(step1())
			.next(step2())
			.build();
}

@Bean
public Flow flow2() {
	return new FlowBuilder<SimpleFlow>("flow2")
			.start(step3())
			.build();
}

@Bean
public Job job(Flow flow1, Flow flow2) {
	return this.jobBuilderFactory.get("job")
				.start(flow1)
				.split(new SimpleAsyncTaskExecutor())
				.add(flow2)
				.next(step4())
				.end()
				.build();
}
```

茶陵後's avatar
茶陵後 已提交
1499
#### 外部化作业之间的流定义和依赖关系
dallascao's avatar
dallascao 已提交
1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604

作业中的部分流可以作为单独的 Bean 定义外部化,然后重新使用。有两种方法可以做到这一点。第一种方法是简单地将流声明为对别处定义的流的引用。

下面的示例展示了如何将流声明为对 XML 中其他地方定义的流的引用:

XML 配置

```
<job id="job">
    <flow id="job1.flow1" parent="flow1" next="step3"/>
    <step id="step3" parent="s3"/>
</job>

<flow id="flow1">
    <step id="step1" parent="s1" next="step2"/>
    <step id="step2" parent="s2"/>
</flow>
```

下面的示例展示了如何将流声明为对 Java 中其他地方定义的流的引用:

Java 配置

```
@Bean
public Job job() {
	return this.jobBuilderFactory.get("job")
				.start(flow1())
				.next(step3())
				.end()
				.build();
}

@Bean
public Flow flow1() {
	return new FlowBuilder<SimpleFlow>("flow1")
			.start(step1())
			.next(step2())
			.build();
}
```

如前面的示例所示,定义外部流的效果是将外部流中的步骤插入到作业中,就好像这些步骤是内联声明的一样。通过这种方式,许多作业可以引用相同的模板流,并将这样的模板组合成不同的逻辑流。这也是分离单个流的集成测试的一种好方法。

外部化流程的另一种形式是使用`JobStep`。a`JobStep`类似于 a`FlowStep`,但实际上是为指定的流程中的步骤创建并启动一个单独的作业执行。

下面的示例是 XML 中`JobStep`的示例:

XML 配置

```
<job id="jobStepJob" restartable="true">
   <step id="jobStepJob.step1">
      <job ref="job" job-launcher="jobLauncher"
          job-parameters-extractor="jobParametersExtractor"/>
   </step>
</job>

<job id="job" restartable="true">...</job>

<bean id="jobParametersExtractor" class="org.spr...DefaultJobParametersExtractor">
   <property name="keys" value="input.file"/>
</bean>
```

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

Java 配置

```
@Bean
public Job jobStepJob() {
	return this.jobBuilderFactory.get("jobStepJob")
				.start(jobStepJobStep1(null))
				.build();
}

@Bean
public Step jobStepJobStep1(JobLauncher jobLauncher) {
	return this.stepBuilderFactory.get("jobStepJobStep1")
				.job(job())
				.launcher(jobLauncher)
				.parametersExtractor(jobParametersExtractor())
				.build();
}

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

@Bean
public DefaultJobParametersExtractor jobParametersExtractor() {
	DefaultJobParametersExtractor extractor = new DefaultJobParametersExtractor();

	extractor.setKeys(new String[]{"input.file"});

	return extractor;
}
```

作业参数提取器是一种策略,它确定如何将`Step``ExecutionContext`转换为正在运行的`JobParameters``JobParameters`。当你希望有一些更细粒度的选项来监视和报告作业和步骤时,`JobStep`非常有用。使用`JobStep`通常也是对这个问题的一个很好的回答:“我如何在工作之间创建依赖关系?”这是一种很好的方法,可以将一个大型系统分解成更小的模块,并控制工作流程。

茶陵後's avatar
茶陵後 已提交
1605
### `Job`和`Step`属性的后期绑定
dallascao's avatar
dallascao 已提交
1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750

前面显示的 XML 和平面文件示例都使用 Spring `Resource`抽象来获取文件。这是因为`Resource`有一个`getFile`方法,它返回一个`java.io.File`。XML 和平面文件资源都可以使用标准的 Spring 构造进行配置:

下面的示例展示了 XML 中的后期绑定:

XML 配置

```
<bean id="flatFileItemReader"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="resource"
              value="file://outputs/file.txt" />
</bean>
```

下面的示例展示了 Java 中的后期绑定:

Java 配置

```
@Bean
public FlatFileItemReader flatFileItemReader() {
	FlatFileItemReader<Foo> reader = new FlatFileItemReaderBuilder<Foo>()
			.name("flatFileItemReader")
			.resource(new FileSystemResource("file://outputs/file.txt"))
			...
}
```

前面的`Resource`从指定的文件系统位置加载文件。请注意,绝对位置必须以双斜杠(`//`)开始。在大多数 Spring 应用程序中,这种解决方案已经足够好了,因为这些资源的名称在编译时是已知的。然而,在批处理场景中,可能需要在运行时确定文件名作为作业的参数。这可以通过使用“-D”参数读取系统属性来解决。

下面的示例展示了如何从 XML 中的属性读取文件名:

XML 配置

```
<bean id="flatFileItemReader"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="resource" value="${input.file.name}" />
</bean>
```

下面展示了如何从 Java 中的属性读取文件名:

Java 配置

```
@Bean
public FlatFileItemReader flatFileItemReader(@Value("${input.file.name}") String name) {
	return new FlatFileItemReaderBuilder<Foo>()
			.name("flatFileItemReader")
			.resource(new FileSystemResource(name))
			...
}
```

要使这个解决方案起作用,所需要的只是一个系统参数(例如`-Dinput.file.name="file://outputs/file.txt"`)。

|   |虽然在这里可以使用`PropertyPlaceholderConfigurer`,但是如果系统属性始终设置,则不需要<br/>,因为 Spring `ResourceEditor`中的<br/>已经对系统属性进行了筛选和占位符替换。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

通常,在批处理设置中,最好是在作业的`JobParameters`中参数化文件名,而不是通过系统属性,并以这种方式访问它们。为了实现这一点, Spring 批处理允许各种`Job``Step`属性的后期绑定。

下面的示例展示了如何用 XML 参数化一个文件名:

XML 配置

```
<bean id="flatFileItemReader" scope="step"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="resource" value="#{jobParameters['input.file.name']}" />
</bean>
```

下面的示例展示了如何在 Java 中参数化一个文件名:

Java 配置

```
@StepScope
@Bean
public FlatFileItemReader flatFileItemReader(@Value("#{jobParameters['input.file.name']}") String name) {
	return new FlatFileItemReaderBuilder<Foo>()
			.name("flatFileItemReader")
			.resource(new FileSystemResource(name))
			...
}
```

`JobExecution``StepExecution`级别`ExecutionContext`都可以以相同的方式访问。

下面的示例展示了如何访问 XML 中的`ExecutionContext`:

XML 配置

```
<bean id="flatFileItemReader" scope="step"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="resource" value="#{jobExecutionContext['input.file.name']}" />
</bean>
```

XML 配置

```
<bean id="flatFileItemReader" scope="step"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="resource" value="#{stepExecutionContext['input.file.name']}" />
</bean>
```

下面的示例展示了如何在 Java 中访问`ExecutionContext`:

Java 配置

```
@StepScope
@Bean
public FlatFileItemReader flatFileItemReader(@Value("#{jobExecutionContext['input.file.name']}") String name) {
	return new FlatFileItemReaderBuilder<Foo>()
			.name("flatFileItemReader")
			.resource(new FileSystemResource(name))
			...
}
```

Java 配置

```
@StepScope
@Bean
public FlatFileItemReader flatFileItemReader(@Value("#{stepExecutionContext['input.file.name']}") String name) {
	return new FlatFileItemReaderBuilder<Foo>()
			.name("flatFileItemReader")
			.resource(new FileSystemResource(name))
			...
}
```

|   |任何使用 late-binding 的 Bean 都必须用 scope=“step”声明。有关更多信息,请参见[Step Scope](#step-scope)。应该注意的是<br/>a`Step` Bean 不应该是步骤作用域。如果在<br/>定义的步骤中需要进行后期绑定,则该步骤的组件(即 tasklet、Item Reader/Writer 等)<br/>是应该被限定范围的组件。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |如果你正在使用 Spring 3.0(或更高版本),则步骤作用域 bean 中的表达式使用<br/> Spring 表达式语言,这是一种功能强大的通用语言,具有许多有趣的<br/>特性。为了提供向后兼容性,如果 Spring 批检测到<br/> Spring 的旧版本的存在,则它使用一种功能不那么强大的原生表达式语言和具有略有不同的解析规则的<br/>。主要的区别在于,在<br/>上面的示例中的 MAP 键不需要引用 Spring 2.5,但是在 Spring 3.0 中的引用是强制性的<br/>。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

茶陵後's avatar
茶陵後 已提交
1751
#### 步骤作用域
dallascao's avatar
dallascao 已提交
1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798

前面显示的所有延迟绑定示例都在 Bean 定义中声明了“步骤”的范围。

下面的示例展示了在 XML 中绑定到 STEP 作用域的示例:

XML 配置

```
<bean id="flatFileItemReader" scope="step"
      class="org.springframework.batch.item.file.FlatFileItemReader">
    <property name="resource" value="#{jobParameters[input.file.name]}" />
</bean>
```

下面的示例展示了在 Java 中绑定到 STEP 作用域的示例:

Java 配置

```
@StepScope
@Bean
public FlatFileItemReader flatFileItemReader(@Value("#{jobParameters[input.file.name]}") String name) {
	return new FlatFileItemReaderBuilder<Foo>()
			.name("flatFileItemReader")
			.resource(new FileSystemResource(name))
			...
}
```

使用 late binding 需要使用`Step`的作用域,因为在`Step`开始之前,实际上不能实例化 Bean,以允许找到属性。因为默认情况下它不是 Spring 容器的一部分,所以必须通过使用`batch`名称空间,或者通过显式地为`StepScope`包含一个 Bean 定义,或者通过使用`@EnableBatchProcessing`注释,显式地添加作用域。只使用其中一种方法。下面的示例使用`batch`名称空间:

```
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:batch="http://www.springframework.org/schema/batch"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="...">
<batch:job .../>
...
</beans>
```

下面的示例明确地包括 Bean 定义:

```
<bean class="org.springframework.batch.core.scope.StepScope" />
```

茶陵後's avatar
茶陵後 已提交
1799
#### 工作范围
dallascao's avatar
dallascao 已提交
1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869

在 Spring 批 3.0 中引入的`Job`作用域在配置中类似于`Step`作用域,但它是`Job`上下文的作用域,因此每个运行的作业只有一个这样的 Bean 实例。此外,还支持使用`#{..}`占位符从`JobContext`访问的引用的后期绑定。使用此特性, Bean 可以从作业或作业执行上下文和作业参数中提取属性。

下面的示例展示了在 XML 中绑定到作业范围的示例:

XML 配置

```
<bean id="..." class="..." scope="job">
    <property name="name" value="#{jobParameters[input]}" />
</bean>
```

XML 配置

```
<bean id="..." class="..." scope="job">
    <property name="name" value="#{jobExecutionContext['input.name']}.txt" />
</bean>
```

下面的示例展示了在 Java 中绑定到作业范围的示例:

Java 配置

```
@JobScope
@Bean
public FlatFileItemReader flatFileItemReader(@Value("#{jobParameters[input]}") String name) {
	return new FlatFileItemReaderBuilder<Foo>()
			.name("flatFileItemReader")
			.resource(new FileSystemResource(name))
			...
}
```

Java 配置

```
@JobScope
@Bean
public FlatFileItemReader flatFileItemReader(@Value("#{jobExecutionContext['input.name']}") String name) {
	return new FlatFileItemReaderBuilder<Foo>()
			.name("flatFileItemReader")
			.resource(new FileSystemResource(name))
			...
}
```

因为默认情况下它不是 Spring 容器的一部分,所以必须通过使用`batch`命名空间,通过显式地为 JobScope 包括一个 Bean 定义,或者使用`@EnableBatchProcessing`注释(但不是所有的),显式地添加范围。下面的示例使用`batch`名称空间:

```
<beans xmlns="http://www.springframework.org/schema/beans"
		  xmlns:batch="http://www.springframework.org/schema/batch"
		  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		  xsi:schemaLocation="...">

<batch:job .../>
...
</beans>
```

下面的示例包括显式定义`JobScope`的 Bean:

```
<bean class="org.springframework.batch.core.scope.JobScope" />
```

|   |在多线程<br/>或分区步骤中使用作业范围的 bean 有一些实际的限制。 Spring 批处理不控制在这些<br/>用例中产生的线程,因此不可能正确地设置它们以使用这样的 bean。因此,<br/>不建议在多线程或分区步骤中使用作业范围的 bean。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|