在许多情况下,处理过程中遇到的错误不应导致Step失败,但应该跳过。这通常是一个必须由了解数据本身及其含义的人做出的决定。例如,财务数据可能无法跳过,因为它会导致资金转移,这需要完全准确。另一方面,加载供应商列表可能允许跳过。如果由于格式不正确或缺少必要信息而未加载供应商,则可能没有问题。通常,这些跳过的记录也会被记录下来,上一节我们在讨论 SkipListener的时候已经知道。
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 配置一个跳过:
@Bean
public Step step1() {
return this.stepBuilderFactory.get("step1")
.<String, String>chunk(10)
.reader(flatFileItemReader())
.writer(itemWriter())
.faultTolerant()
.skipLimit(10)
.skip(FlatFileParseException.class)
.build();
}
上边我对FlatFileParseException异常设置了10次跳过,这个限制是 Reader/Processor/Writer 分别进行计数,当其中一个达到第11次的时候,就会直接抛出这个FlatFileParseException异常。
但是上边的例子有个问题就是只跳过FlatFileParseException异常,其他所有的异常都会被标记为失败。在某些情况下,这可能是正确的行为。但是,在其他情况下,可能更容易识别哪些异常应导致失败并跳过其他所有内容。在 Spring Batch 中提供了include 和 exclude(Java配置为skip和noSkip方法调用)来实现这种需求:
<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 配置:
@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标识为可跳过的异常类,但是,通过'exclude'(noSkip) 将 java.io.FileNotFoundException异常排除。这就是说这个 Batch 跳过除java.io.FileNotFoundException之外的所有异常,如果遇到java.io.FileNotFoundException异常则提示 Batch 失败。
除了通过include/exclude 来配置跳过之外,Batch 还提供了一种自由度更高的配置,跳过策略(SkipPolicy),定义接口如下:
public interface SkipPolicy {
boolean shouldSkip(Throwable t, int skipCount) throws SkipLimitExceededException;
}
Spring Batch 提供默认实现类如下:
下边我们就通过源码来看看具看这些实现类都是实现了什么策略:
AlwaysSkipItemSkipPolicy
我们先来看AlwaysSkipItemSkipPolicy的源码,发现是直接返回了 True 也就是,无论发生什么异常,都会被跳过。
public class AlwaysSkipItemSkipPolicy implements SkipPolicy {
@Override
public boolean shouldSkip(Throwable t, int skipCount) {
return true;
}
}
CompositeSkipPolicy
同样我们打开CompositeSkipPolicy的源码,可以发现CompositeSkipPolicy是一种组合的跳过策略,同时我们可以发现,对于组合策略,只要有一个满足跳过条件,整个策略都是跳过的。
public class CompositeSkipPolicy implements SkipPolicy {
private SkipPolicy[] skipPolicies;
@Override
public boolean shouldSkip(Throwable t, int skipCount) throws SkipLimitExceededException {
for (SkipPolicy policy : skipPolicies) {
if (policy.shouldSkip(t, skipCount)) {
return true;
}
}
return false;
}
}
ExceptionClassifierSkipPolicy
分析 ExceptionClassifierSkipPolicy的源码,我们发现ExceptionClassifierSkipPolicy会根据当前线程中的 SkipPolicy 来设置跳过的策略。
public class ExceptionClassifierSkipPolicy implements SkipPolicy {
private SubclassClassifier<Throwable, SkipPolicy> classifier;
@Override
public boolean shouldSkip(Throwable t, int skipCount) throws SkipLimitExceededException {
return classifier.classify(t).shouldSkip(t, skipCount);
}
}
LimitCheckingItemSkipPolicy
可以设置跳过次数的跳过策略,也是 Spring Batch 的默认策略。
public class LimitCheckingItemSkipPolicy implements SkipPolicy {
private int skipLimit;
@Override
public boolean shouldSkip(Throwable t, int skipCount) {
if (skippableExceptionClassifier.classify(t)) {
if (skipCount < skipLimit) {
return true;
}
else {
throw new SkipLimitExceededException(skipLimit, t);
}
}
else {
return false;
}
}
}
NeverSkipItemSkipPolicy
和AlwaysSkipItemSkipPolicy正好相反,无论发生任何异常,都不会被跳过。
public class NeverSkipItemSkipPolicy implements SkipPolicy{
@Override
public boolean shouldSkip(Throwable t, int skipCount) {
return false;
}
}
记录被跳过之后需要记录掉过的记录,这个时候就需要我们上一节说的 SkipListener 来实现,不熟悉的同学不妨再回头去看看。