# 项目处理

## 项处理

XMLJavaBoth

[ItemReader 和 ItemWriter 接口](readersAndWriters.html#readersAndWriters)对于它们的特定任务都非常有用，但是如果你想在编写之前插入业务逻辑呢？读和写的一个选项是使用复合模式：创建一个`ItemWriter`，其中包含另一个`ItemWriter`或一个`ItemReader`，其中包含另一个`ItemReader`。下面的代码展示了一个示例：

```
public class CompositeItemWriter<T> implements ItemWriter<T> {

    ItemWriter<T> itemWriter;

    public CompositeItemWriter(ItemWriter<T> itemWriter) {
        this.itemWriter = itemWriter;
    }

    public void write(List<? extends T> items) throws Exception {
        //Add business logic here
       itemWriter.write(items);
    }

    public void setDelegate(ItemWriter<T> itemWriter){
        this.itemWriter = itemWriter;
    }
}
```

前面的类包含另一个`ItemWriter`，它在提供了一些业务逻辑之后将其委托给它。这种模式也可以很容易地用于`ItemReader`，也许可以基于由主`ItemReader`提供的输入来获得更多的引用数据。如果你需要自己控制对`write`的调用，它也很有用。但是，如果你只想在实际写入之前“转换”传入的用于写入的项，则不需要`write`你自己。你只需修改项目即可。对于此场景， Spring Batch 提供了`ItemProcessor`接口，如下面的接口定义所示：

```
public interface ItemProcessor<I, O> {

    O process(I item) throws Exception;
}
```

`ItemProcessor`很简单。给定一个对象，将其转换并返回另一个对象。所提供的对象可以是相同类型的，也可以不是相同类型的。关键在于，业务逻辑可以应用于流程中，完全由开发人员来创建该逻辑。`ItemProcessor`可以直接连接到一个步骤。例如，假设`ItemReader`提供了类型`Foo`的类，并且在写出之前需要将其转换为类型`Bar`。下面的示例显示了执行转换的`ItemProcessor`:

```
public class Foo {}

public class Bar {
    public Bar(Foo foo) {}
}

public class FooProcessor implements ItemProcessor<Foo, Bar> {
    public Bar process(Foo foo) throws Exception {
        //Perform simple transformation, convert a Foo to a Bar
        return new Bar(foo);
    }
}

public class BarWriter implements ItemWriter<Bar> {
    public void write(List<? extends Bar> bars) throws Exception {
        //write bars
    }
}
```

在前面的示例中，有一个类`Foo`，一个类`Bar`，以及一个类`FooProcessor`，它坚持`ItemProcessor`接口。转换很简单，但是任何类型的转换都可以在这里完成。`BarWriter`写`Bar`对象，如果提供了任何其他类型，则抛出异常。类似地，如果只提供了`Foo`，则`FooProcessor`抛出异常。然后可以将`FooProcessor`注入`Step`，如下例所示：

XML 配置

```
<job id="ioSampleJob">
    <step name="step1">
        <tasklet>
            <chunk reader="fooReader" processor="fooProcessor" writer="barWriter"
                   commit-interval="2"/>
        </tasklet>
    </step>
</job>
```

Java 配置

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

@Bean
public Step step1() {
	return this.stepBuilderFactory.get("step1")
				.<Foo, Bar>chunk(2)
				.reader(fooReader())
				.processor(fooProcessor())
				.writer(barWriter())
				.build();
}
```

`ItemProcessor`与`ItemReader`或`ItemWriter`之间的区别在于，`ItemProcessor`对于`Step`是可选的。

### 链接项目处理器

在许多场景中，执行单个转换是有用的，但是如果你想将多个`ItemProcessor`实现“链”在一起，该怎么办？这可以使用前面提到的复合模式来完成。为了更新前面的单个转换，例如，将`Foo`转换为`Bar`，将其转换为`Foobar`并写出，如以下示例所示：

```
public class Foo {}

public class Bar {
    public Bar(Foo foo) {}
}

public class Foobar {
    public Foobar(Bar bar) {}
}

public class FooProcessor implements ItemProcessor<Foo, Bar> {
    public Bar process(Foo foo) throws Exception {
        //Perform simple transformation, convert a Foo to a Bar
        return new Bar(foo);
    }
}

public class BarProcessor implements ItemProcessor<Bar, Foobar> {
    public Foobar process(Bar bar) throws Exception {
        return new Foobar(bar);
    }
}

public class FoobarWriter implements ItemWriter<Foobar>{
    public void write(List<? extends Foobar> items) throws Exception {
        //write items
    }
}
```

a`FooProcessor`和 a`BarProcessor`可以’链接’在一起，以得到结果`Foobar`，如以下示例所示：

```
CompositeItemProcessor<Foo,Foobar> compositeProcessor =
                                      new CompositeItemProcessor<Foo,Foobar>();
List itemProcessors = new ArrayList();
itemProcessors.add(new FooProcessor());
itemProcessors.add(new BarProcessor());
compositeProcessor.setDelegates(itemProcessors);
```

正如前面的示例一样，复合处理器可以配置为`Step`:

XML 配置

```
<job id="ioSampleJob">
    <step name="step1">
        <tasklet>
            <chunk reader="fooReader" processor="compositeItemProcessor" writer="foobarWriter"
                   commit-interval="2"/>
        </tasklet>
    </step>
</job>

<bean id="compositeItemProcessor"
      class="org.springframework.batch.item.support.CompositeItemProcessor">
    <property name="delegates">
        <list>
            <bean class="..FooProcessor" />
            <bean class="..BarProcessor" />
        </list>
    </property>
</bean>
```

Java 配置

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

@Bean
public Step step1() {
	return this.stepBuilderFactory.get("step1")
				.<Foo, Foobar>chunk(2)
				.reader(fooReader())
				.processor(compositeProcessor())
				.writer(foobarWriter())
				.build();
}

@Bean
public CompositeItemProcessor compositeProcessor() {
	List<ItemProcessor> delegates = new ArrayList<>(2);
	delegates.add(new FooProcessor());
	delegates.add(new BarProcessor());

	CompositeItemProcessor processor = new CompositeItemProcessor();

	processor.setDelegates(delegates);

	return processor;
}
```

### 过滤记录

项目处理器的一个典型用途是在将记录传递给`ItemWriter`之前过滤掉它们。过滤是一种不同于跳过的动作。跳过表示记录无效，而筛选只表示不应写入记录。

例如，考虑一个批处理作业，它读取包含三种不同类型记录的文件：要插入的记录、要更新的记录和要删除的记录。如果系统不支持记录删除，那么我们将不希望将任何“delete”记录发送到`ItemWriter`。但是，由于这些记录实际上并不是不良记录，我们希望过滤掉它们，而不是跳过它们。因此，`ItemWriter`将只接收“插入”和“更新”记录。

要过滤记录，可以从`ItemProcessor`返回`null`。该框架检测到结果是`null`，并避免将该项添加到交付给`ItemWriter`的记录列表中。像往常一样，从`ItemProcessor`抛出的异常会导致跳过。

### 验证输入

在[项目阅读器和项目编写器](readersAndWriters.html#readersAndWriters)章中，讨论了多种解析输入的方法。如果不是“格式良好”的，每个主要实现都会抛出一个异常。如果缺少数据范围，`FixedLengthTokenizer`将抛出一个异常。类似地，试图访问`RowMapper`或`FieldSetMapper`中不存在或格式与预期不同的索引，会引发异常。所有这些类型的异常都是在`read`返回之前抛出的。但是，它们没有解决返回的项目是否有效的问题。例如，如果其中一个字段是年龄，那么它显然不可能是负的。它可以正确地解析，因为它存在并且是一个数字，但是它不会导致异常。由于已经有过多的验证框架， Spring Batch 不会尝试提供另一种验证框架。相反，它提供了一个名为`Validator`的简单接口，可以由任意数量的框架实现，如以下接口定义所示：

```
public interface Validator<T> {

    void validate(T value) throws ValidationException;

}
```

契约是，如果对象无效，`validate`方法抛出一个异常，如果对象有效，则正常返回。 Spring 批处理提供了开箱即用的`ValidatingItemProcessor`，如以下 Bean 定义所示：

XML 配置

```
<bean class="org.springframework.batch.item.validator.ValidatingItemProcessor">
    <property name="validator" ref="validator" />
</bean>

<bean id="validator" class="org.springframework.batch.item.validator.SpringValidator">
	<property name="validator">
		<bean class="org.springframework.batch.sample.domain.trade.internal.validator.TradeValidator"/>
	</property>
</bean>
```

Java 配置

```
@Bean
public ValidatingItemProcessor itemProcessor() {
	ValidatingItemProcessor processor = new ValidatingItemProcessor();

	processor.setValidator(validator());

	return processor;
}

@Bean
public SpringValidator validator() {
	SpringValidator validator = new SpringValidator();

	validator.setValidator(new TradeValidator());

	return validator;
}
```

你还可以使用`BeanValidatingItemProcessor`来验证用 Bean 验证 API（JSR-303）注释的项。例如，给定以下类型`Person`:

```
class Person {

    @NotEmpty
    private String name;

    public Person(String name) {
     this.name = name;
    }

    public String getName() {
     return name;
    }

    public void setName(String name) {
     this.name = name;
    }

}
```

可以通过在应用程序上下文中声明`BeanValidatingItemProcessor` Bean 来验证项，并在面向块的步骤中将其注册为处理器：

```
@Bean
public BeanValidatingItemProcessor<Person> beanValidatingItemProcessor() throws Exception {
    BeanValidatingItemProcessor<Person> beanValidatingItemProcessor = new BeanValidatingItemProcessor<>();
    beanValidatingItemProcessor.setFilter(true);

    return beanValidatingItemProcessor;
}
```

### 容错

当块被回滚时，在读取过程中缓存的项可能会被重新处理。如果一个步骤被配置为容错（通常通过使用跳过或重试处理），则所使用的任何`ItemProcessor`都应该以幂等的方式实现。通常，这将包括对`ItemProcessor`的输入项不执行任何更改，并且只更新结果中的实例。