# 项目处理
# 项处理
XMLJavaBoth
ItemReader 和 ItemWriter 接口对于它们的特定任务都非常有用,但是如果你想在编写之前插入业务逻辑呢?读和写的一个选项是使用复合模式:创建一个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
}
}
aFooProcessor
和 aBarProcessor
可以’链接’在一起,以得到结果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
抛出的异常会导致跳过。
# 验证输入
在项目阅读器和项目编写器章中,讨论了多种解析输入的方法。如果不是“格式良好”的,每个主要实现都会抛出一个异常。如果缺少数据范围,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
的输入项不执行任何更改,并且只更新结果中的实例。
← 项目阅读器和项目编写器 缩放和并行处理 →