# 项目处理 ## 项处理 XMLJavaBoth [ItemReader 和 ItemWriter 接口](readersAndWriters.html#readersAndWriters)对于它们的特定任务都非常有用,但是如果你想在编写之前插入业务逻辑呢?读和写的一个选项是使用复合模式:创建一个`ItemWriter`,其中包含另一个`ItemWriter`或一个`ItemReader`,其中包含另一个`ItemReader`。下面的代码展示了一个示例: ``` public class CompositeItemWriter implements ItemWriter { ItemWriter itemWriter; public CompositeItemWriter(ItemWriter itemWriter) { this.itemWriter = itemWriter; } public void write(List items) throws Exception { //Add business logic here itemWriter.write(items); } public void setDelegate(ItemWriter itemWriter){ this.itemWriter = itemWriter; } } ``` 前面的类包含另一个`ItemWriter`,它在提供了一些业务逻辑之后将其委托给它。这种模式也可以很容易地用于`ItemReader`,也许可以基于由主`ItemReader`提供的输入来获得更多的引用数据。如果你需要自己控制对`write`的调用,它也很有用。但是,如果你只想在实际写入之前“转换”传入的用于写入的项,则不需要`write`你自己。你只需修改项目即可。对于此场景, Spring Batch 提供了`ItemProcessor`接口,如下面的接口定义所示: ``` public interface ItemProcessor { 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 { 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 { public void write(List bars) throws Exception { //write bars } } ``` 在前面的示例中,有一个类`Foo`,一个类`Bar`,以及一个类`FooProcessor`,它坚持`ItemProcessor`接口。转换很简单,但是任何类型的转换都可以在这里完成。`BarWriter`写`Bar`对象,如果提供了任何其他类型,则抛出异常。类似地,如果只提供了`Foo`,则`FooProcessor`抛出异常。然后可以将`FooProcessor`注入`Step`,如下例所示: XML 配置 ``` ``` Java 配置 ``` @Bean public Job ioSampleJob() { return this.jobBuilderFactory.get("ioSampleJob") .start(step1()) .build(); } @Bean public Step step1() { return this.stepBuilderFactory.get("step1") .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 { 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 { public Foobar process(Bar bar) throws Exception { return new Foobar(bar); } } public class FoobarWriter implements ItemWriter{ public void write(List items) throws Exception { //write items } } ``` a`FooProcessor`和 a`BarProcessor`可以’链接’在一起,以得到结果`Foobar`,如以下示例所示: ``` CompositeItemProcessor compositeProcessor = new CompositeItemProcessor(); List itemProcessors = new ArrayList(); itemProcessors.add(new FooProcessor()); itemProcessors.add(new BarProcessor()); compositeProcessor.setDelegates(itemProcessors); ``` 正如前面的示例一样,复合处理器可以配置为`Step`: XML 配置 ``` ``` Java 配置 ``` @Bean public Job ioSampleJob() { return this.jobBuilderFactory.get("ioSampleJob") .start(step1()) .build(); } @Bean public Step step1() { return this.stepBuilderFactory.get("step1") .chunk(2) .reader(fooReader()) .processor(compositeProcessor()) .writer(foobarWriter()) .build(); } @Bean public CompositeItemProcessor compositeProcessor() { List 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 { void validate(T value) throws ValidationException; } ``` 契约是,如果对象无效,`validate`方法抛出一个异常,如果对象有效,则正常返回。 Spring 批处理提供了开箱即用的`ValidatingItemProcessor`,如以下 Bean 定义所示: XML 配置 ``` ``` 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 beanValidatingItemProcessor() throws Exception { BeanValidatingItemProcessor beanValidatingItemProcessor = new BeanValidatingItemProcessor<>(); beanValidatingItemProcessor.setFilter(true); return beanValidatingItemProcessor; } ``` ### 容错 当块被回滚时,在读取过程中缓存的项可能会被重新处理。如果一个步骤被配置为容错(通常通过使用跳过或重试处理),则所使用的任何`ItemProcessor`都应该以幂等的方式实现。通常,这将包括对`ItemProcessor`的输入项不执行任何更改,并且只更新结果中的实例。