Spring Batch任务陷入循环,ItemReader返回null仍未终止
我帮你梳理下代码里的几个关键问题,这些大概率是导致任务陷入无限循环的根源:
1. CommReader的read方法逻辑缺陷
你的CommReader.read()方法存在一个致命问题:只要文件存在,每次调用read()都会重新读取整个XML文件并返回Document对象,永远不会主动返回null(除非文件被删除/移走)。
Spring Batch的Step循环逻辑是:持续调用read()直到返回null,才会结束chunk处理并进入后续流程。而你的Reader每次都返回同一个Document,Batch就会认为还有数据要处理,无限循环下去。
修复方案
给CommReader添加一个标记位,记录文件是否已经被读取过,确保read()仅返回一次数据,之后返回null:
public class CommReader implements ItemReader<Object> { private File inputFile; private boolean hasRead = false; // 新增标记位:记录是否已读取文件 @Value("#{stepExecution}") private StepExecution stepExecution; public CommReader(String inputFilePath) { inputFile = new File(inputFilePath); } @Override public Object read() throws IOException { // 已读取过文件,或文件不存在时直接返回null if (hasRead || !inputFile.exists()) { return null; } DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder; try { builder = factory.newDocumentBuilder(); log.info("CommReader.read() :" + inputFile.getAbsolutePath()); Document document = builder.parse(inputFile); hasRead = true; // 标记为已读取 return document; } catch (ParserConfigurationException | SAXException | TransformerFactoryConfigurationError e) { log.error("Exception while reading ", e); hasRead = true; // 出错后也标记,避免循环 return null; } } // 完善ItemStream接口实现,支持Step重启时恢复状态 @Override public void open(ExecutionContext executionContext) throws ItemStreamException { this.hasRead = executionContext.containsKey("hasRead") && executionContext.getBoolean("hasRead"); } @Override public void update(ExecutionContext executionContext) throws ItemStreamException { executionContext.put("hasRead", this.hasRead); } // 其他原有方法保持不变 }
2. 文件移动时机的问题
你的文件流转逻辑是:在JobListener.afterJob()里才移动文件,但此时Step还在循环读取文件(因为Reader一直返回非null),文件始终存在,导致read()永远不会返回null。
正确的流程应该是:
- 在Integration Flow的
moveFile步骤,就把文件从landing目录移到inprocess目录 - CommReader读取的是inprocess目录的文件,读取完成后(或出错时),再在合适的时机移到completed/error目录
另外要确认moveFile方法确实成功移动了文件,避免原文件还留在landing目录被重复轮询处理。
3. Chunk配置与Reader返回值的不匹配
你的Step配置了chunk size(从Const.INPUT_READ_CHUNK_SIZE读取),但你的Reader每次返回的是整个XML文件的Document对象(相当于一个Item)。如果chunk size大于1,Batch会尝试多次调用read()凑够chunk size,但你的Reader每次都返回同一个Document,这也会加剧循环问题。
建议根据你的业务调整:要么让Reader按XML节点拆分返回单个Item,要么把chunk size设为1,确保一次处理整个XML文件。
4. ItemWriter的逻辑问题(非循环直接原因,但需优化)
你的CommItemWriter.write()方法里,每次处理一个Item(整个Document)都会重新生成一次输出文件,这会导致后续的Item覆盖之前的输出。如果你的需求是把单个XML转成单个CSV,应该调整为:
- 在open()方法中初始化输出文件
- 在write()方法中写入内容(不要每次都重新创建文件)
- 在close()方法中完成文件写入并关闭流
总结修复步骤
- 给CommReader添加已读取标记位,确保read()仅返回一次数据
- 确认Integration Flow的文件移动逻辑正确,Reader读取的是inprocess目录的文件
- 调整chunk size与Reader返回值匹配,避免无效的多次读取
- 完善ItemStream接口实现,支持Step重启时的状态恢复
- 优化ItemWriter的文件写入逻辑,避免重复覆盖输出
内容的提问来源于stack exchange,提问作者Aniruddha P

