如何处理Spring Batch从步骤中读取器的FlatFileParseException异常
嘿,这个场景我太熟悉了!在Spring Batch处理多CSV文件并行解析时碰到FlatFileParseException真的很常见,尤其是测试阶段故意埋坑的时候😉。咱们可以从几个层面来处理这个问题,既保证作业不崩溃,又能清晰记录错误信息,还能灵活处理出错的文件:
1. 先让作业“容错”:配置跳过解析异常
Spring Batch的容错机制可以帮你跳过有问题的行,而不是直接终止整个作业。你只需要在从步骤(slave step)的配置里开启faultTolerant(),并指定允许跳过的异常类型和最大跳过次数:
@Bean public Step slaveStep(StepBuilderFactory stepBuilderFactory, ItemReader<User> csvItemReader, ItemProcessor<User, User> userProcessor, ItemWriter<User> userWriter, CustomSkipListener skipListener) { return stepBuilderFactory.get("slaveStep") .<User, User>chunk(10) .reader(csvItemReader) .processor(userProcessor) .writer(userWriter) .faultTolerant() // 指定允许跳过FlatFileParseException .skip(FlatFileParseException.class) // 最多跳过10条错误行(根据你的需求调整) .skipLimit(10) // 添加跳过监听器,记录错误详情 .listener(skipListener) .build(); }
2. 记录错误详情:自定义SkipListener
光跳过还不够,你得知道哪行错了、错在哪,方便后续排查。写一个自定义的SkipListener,专门捕获解析异常并记录关键信息:
@Component public class CustomSkipListener implements SkipListener<User, User> { private static final Logger logger = LoggerFactory.getLogger(CustomSkipListener.class); @Override public void onSkipInRead(Throwable t) { if (t instanceof FlatFileParseException) { FlatFileParseException parseEx = (FlatFileParseException) t; logger.error("⚠️ 文件 {} 的第 {} 行解析失败,错误内容:{}", parseEx.getResource().getFilename(), parseEx.getLineNumber(), parseEx.getInput(), parseEx); } } }
这样日志里就会清晰显示出错的文件、行号和具体内容,排查起来非常方便。
3. 灵活处理出错的文件:结合Step监听器
如果你的需求是“只要文件里有错误,就不把它移到已处理文件夹,而是放到错误文件夹”,那可以再加一个StepExecutionListener,在步骤结束后根据跳过次数判断文件的去向:
@Component public class FilePostProcessingListener extends StepExecutionListenerSupport { // 这个路径可以通过Partitioner传递给每个从步骤 @Value("#{stepExecutionContext['filePath']}") private String currentFilePath; @Override public ExitStatus afterStep(StepExecution stepExecution) { // 检查是否有跳过的行 if (stepExecution.getSkipCount() > 0) { // 移到错误文件夹 moveFile(currentFilePath, "/path/to/error-dir/"); return ExitStatus.FAILED.addExitDescription("文件包含解析错误"); } else { // 移到已处理文件夹 moveFile(currentFilePath, "/path/to/processed-dir/"); return ExitStatus.COMPLETED; } } private void moveFile(String sourcePath, String targetDir) { File sourceFile = new File(sourcePath); File targetFile = new File(targetDir + sourceFile.getName()); // 实现文件移动逻辑,注意处理IO异常 try { Files.move(sourceFile.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (IOException e) { logger.error("移动文件失败:{}", sourcePath, e); } } }
记得把这个监听器也加到从步骤的配置里哦。
4. 进阶:自定义跳过策略(复杂场景)
如果你的跳过逻辑更复杂(比如只跳过特定内容的错误行,或者根据错误类型决定是否跳过),可以实现SkipPolicy接口:
public class CustomSkipPolicy implements SkipPolicy { @Override public boolean shouldSkip(Throwable t, long skipCount) throws SkipLimitExceededException { if (t instanceof FlatFileParseException) { FlatFileParseException parseEx = (FlatFileParseException) t; // 比如:只跳过包含"invalid"关键词的错误行,且跳过次数不超过5 return parseEx.getInput().contains("invalid") && skipCount < 5; } // 其他异常不跳过 return false; } }
然后在步骤配置里替换原来的skip()和skipLimit():
.faultTolerant() .skipPolicy(new CustomSkipPolicy())
这样就能实现更精细化的跳过控制了。
总结一下:根据你的需求选择合适的处理方式——如果是个别错误行,跳过并记录;如果是整个文件有问题,就单独归档错误文件。这样既能保证作业的稳定性,又能保留错误信息方便后续处理。
内容的提问来源于stack exchange,提问作者Kush Sahu
相关产品推荐
相关产品推荐

