# 重复

## 重复

XMLJavaBoth

### repeatemplate

批处理是关于重复的操作，或者作为简单的优化，或者作为工作的一部分。 Spring Batch 具有`RepeatOperations`接口，可以对重复进行策略规划和推广，并提供相当于迭代器框架的内容。`RepeatOperations`接口具有以下定义：

```
public interface RepeatOperations {

    RepeatStatus iterate(RepeatCallback callback) throws RepeatException;

}
```

回调是一个接口，如以下定义所示，它允许你插入一些要重复的业务逻辑：

```
public interface RepeatCallback {

    RepeatStatus doInIteration(RepeatContext context) throws Exception;

}
```

回调会重复执行，直到实现确定迭代应该结束为止。这些接口中的返回值是一个枚举，可以是`RepeatStatus.CONTINUABLE`或`RepeatStatus.FINISHED`。一个`RepeatStatus`枚举向重复操作的调用者传递有关是否还有更多工作要做的信息。一般来说，`RepeatOperations`的实现应该检查`RepeatStatus`，并将其用作结束迭代的决策的一部分。任何希望向调用者发出信号表示没有更多工作要做的回调都可以返回`RepeatStatus.FINISHED`。

`RepeatOperations`最简单的通用实现是`RepeatTemplate`，如下例所示：

```
RepeatTemplate template = new RepeatTemplate();

template.setCompletionPolicy(new SimpleCompletionPolicy(2));

template.iterate(new RepeatCallback() {

    public RepeatStatus doInIteration(RepeatContext context) {
        // Do stuff in batch...
        return RepeatStatus.CONTINUABLE;
    }

});
```

在前面的示例中，我们返回`RepeatStatus.CONTINUABLE`，以表明还有更多的工作要做。回调还可以返回`RepeatStatus.FINISHED`，向调用者发出信号，表示没有更多的工作要做。一些迭代可以由回调中所做的工作固有的考虑因素来终止。就回调而言，其他方法实际上是无限循环，并且完成决策被委托给外部策略，如前面示例中所示的情况。

#### repeatcontext

`RepeatCallback`的方法参数是`RepeatContext`。许多回调忽略了上下文。但是，如果有必要，它可以作为一个属性包来存储迭代期间的瞬态数据。在`iterate`方法返回后，上下文不再存在。

如果正在进行嵌套的迭代，则`RepeatContext`具有父上下文。父上下文有时用于存储需要在对`iterate`的调用之间共享的数据。例如，如果你想计算迭代中某个事件发生的次数，并在随后的调用中记住它，那么就是这种情况。

#### 重复状态

`RepeatStatus`是 Spring 批处理用来指示处理是否已经完成的枚举。它有两个可能的`RepeatStatus`值，如下表所示：

|  *Value*  |*说明*|
|-----------|--------------------------------------|
|CONTINUABLE|还有更多的工作要做。|
| FINISHED  |不应再重复。|

`RepeatStatus`值也可以通过在`RepeatStatus`中使用`and()`方法与逻辑和操作结合。这样做的效果是在可持续的标志上做一个合乎逻辑的操作。换句话说，如果任一状态是`FINISHED`，则结果是`FINISHED`。

### 完工政策

在`RepeatTemplate`内，`iterate`方法中的循环的终止由`CompletionPolicy`确定，这也是`RepeatContext`的工厂。`RepeatTemplate`负责使用当前策略创建`RepeatContext`，并在迭代的每个阶段将其传递给`RepeatCallback`。回调完成其`doInIteration`后，`RepeatTemplate`必须调用`CompletionPolicy`，以要求它更新其状态（该状态将存储在`RepeatContext`中）。然后，它询问策略迭代是否完成。

Spring 批处理提供了`CompletionPolicy`的一些简单的通用实现。`SimpleCompletionPolicy`允许执行多达固定的次数（与`RepeatStatus.FINISHED`一起强制在任何时间提前完成）。

对于更复杂的决策，用户可能需要实现自己的完成策略。例如，一旦联机系统投入使用，一个批处理窗口就会阻止批处理作业的执行，这将需要一个自定义策略。

### 异常处理

如果在`RepeatCallback`中抛出了异常，则`RepeatTemplate`查询`ExceptionHandler`，该查询可以决定是否重新抛出异常。

下面的清单显示了`ExceptionHandler`接口定义：

```
public interface ExceptionHandler {

    void handleException(RepeatContext context, Throwable throwable)
        throws Throwable;

}
```

一个常见的用例是计算给定类型的异常数量，并在达到限制时失败。为此目的， Spring 批提供了`SimpleLimitExceptionHandler`和稍微更灵活的`RethrowOnThresholdExceptionHandler`。`SimpleLimitExceptionHandler`具有一个极限属性和一个异常类型，应该将其与当前异常进行比较。所提供类型的所有子类也被计算在内。给定类型的异常将被忽略，直到达到限制，然后重新抛出它们。其他类型的异常总是被重新抛出。

`SimpleLimitExceptionHandler`的一个重要的可选属性是名为`useParent`的布尔标志。默认情况下它是`false`，因此该限制仅在当前的`RepeatContext`中考虑。当设置为`true`时，该限制在嵌套迭代中跨兄弟上下文（例如步骤中的一组块）保持不变。

### 听众

通常情况下，能够接收跨多个不同迭代的交叉关注点的额外回调是有用的。为此， Spring Batch 提供了`RepeatListener`接口。`RepeatTemplate`允许用户注册`RepeatListener`实现，并且在迭代期间可用的情况下，他们将获得带有`RepeatContext`和`RepeatStatus`的回调。

`RepeatListener`接口具有以下定义：

```
public interface RepeatListener {
    void before(RepeatContext context);
    void after(RepeatContext context, RepeatStatus result);
    void open(RepeatContext context);
    void onError(RepeatContext context, Throwable e);
    void close(RepeatContext context);
}
```

`open`和`close`回调出现在整个迭代之前和之后。`before`，`after`，和`onError`应用于单独的`RepeatCallback`调用。

请注意，当有多个侦听器时，它们在一个列表中，因此有一个顺序。在这种情况下，`open`和`before`的调用顺序相同，而`after`、`onError`和`close`的调用顺序相反。

### 并行处理

`RepeatOperations`的实现不限于按顺序执行回调。一些实现能够并行地执行它们的回调，这一点非常重要。为此， Spring Batch 提供了`TaskExecutorRepeatTemplate`，它使用 Spring `TaskExecutor`策略来运行`RepeatCallback`。默认值是使用`SynchronousTaskExecutor`，其效果是在相同的线程中执行整个迭代（与正常的`RepeatTemplate`相同）。

### 声明式迭代

有时，你知道有一些业务处理在每次发生时都想要重复。这方面的经典示例是消息管道的优化。如果一批消息经常到达，那么处理它们比为每条消息承担单独事务的成本更有效。 Spring Batch 提供了一个 AOP 拦截器，该拦截器仅为此目的将方法调用包装在`RepeatOperations`对象中。将`RepeatOperationsInterceptor`执行所截获的方法并根据所提供的`CompletionPolicy`中的`RepeatTemplate`进行重复。

下面的示例展示了使用 Spring  AOP 命名空间来重复对名为`processMessage`的方法的服务调用的声明性迭代（有关如何配置 AOP 拦截器的更多详细信息，请参见 Spring 用户指南）：

```
<aop:config>
    <aop:pointcut id="transactional"
        expression="execution(* com..*Service.processMessage(..))" />
    <aop:advisor pointcut-ref="transactional"
        advice-ref="retryAdvice" order="-1"/>
</aop:config>

<bean id="retryAdvice" class="org.spr...RepeatOperationsInterceptor"/>
```

下面的示例演示了如何使用 Java 配置来重复对一个名为`processMessage`的方法的服务调用（有关如何配置 AOP 拦截器的更多详细信息，请参见 Spring 用户指南）：

```
@Bean
public MyService myService() {
	ProxyFactory factory = new ProxyFactory(RepeatOperations.class.getClassLoader());
	factory.setInterfaces(MyService.class);
	factory.setTarget(new MyService());

	MyService service = (MyService) factory.getProxy();
	JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
	pointcut.setPatterns(".*processMessage.*");

	RepeatOperationsInterceptor interceptor = new RepeatOperationsInterceptor();

	((Advised) service).addAdvisor(new DefaultPointcutAdvisor(pointcut, interceptor));

	return service;
}
```

前面的示例在拦截器内部使用默认的`RepeatTemplate`。要更改策略、侦听器和其他详细信息，可以将`RepeatTemplate`的实例注入拦截器。

如果截获的方法返回`void`，那么拦截器总是返回`RepeatStatus.CONTINUABLE`（因此，如果`CompletionPolicy`没有有限的端点，则存在无限循环的危险）。否则，它将返回`RepeatStatus.CONTINUABLE`，直到截获的方法的返回值是`null`，此时它将返回`RepeatStatus.FINISHED`。因此，目标方法中的业务逻辑可以通过返回`null`或抛出一个异常来表示没有更多的工作要做，该异常是由提供的`ExceptionHandler`中的`RepeatTemplate`重新抛出的。
