Spring Batch下JsonItemReader读取JSON后删除文件报占用问题咨询
问题根因
文件删除时提示目标文件处于打开状态,本质是两个配置疏漏叠加导致的:
- Spring Batch 4.3.0 版本之前存在已知缺陷:JsonItemReader 的
close()方法未主动调用关联 JsonObjectReader 的资源释放逻辑,导致底层包装的FileInputStream、JacksonJsonParser始终持有目标文件的读锁,未在Step执行结束后正常销毁。 - 删除逻辑绑定在 JobExecutionListener.afterJob 回调时,存在时序偏差:部分场景下回调触发早于Spring Batch内部的ItemStream组件销毁流程,此时读取句柄尚未完全释放。
分步修复方案
- 第一步:强制绑定ItemReader的资源关闭钩子
不要完全依赖框架自动触发的关闭逻辑,构建JsonItemReader时显式增加资源销毁回调,兜底覆盖所有关联流的释放:@Bean @StepScope public JsonItemReader<MyObj> jsonItemReader() { Path sourcePath = Paths.get("/data/input/source.json"); FileSystemResource resource = new FileSystemResource(sourcePath); JacksonJsonObjectReader<MyObj> jsonObjectReader = new JacksonJsonObjectReader<>(MyObj.class); // 绑定自定义ObjectMapper映射规则 jsonObjectReader.setMapper(customObjectMapper); JsonItemReader<MyObj> reader = new JsonItemReaderBuilder<MyObj>() .resource(resource) .jsonObjectReader(jsonObjectReader) .name("jsonItemReader") .build(); // 显式注入close逻辑,强制释放关联资源 reader.setStreams(new ItemStream[] { new ItemStreamSupport() { @Override public void close(ExecutionContext executionContext) { try { // 强制调用JsonObjectReader的close,释放内部持有的JsonParser和关联的FileInputStream jsonObjectReader.close(); } catch (IOException e) { log.warn("JSON读取资源关闭异常", e); } } } }); return reader; } - 第二步:迁移删除逻辑到StepExecutionListener的afterStep回调
该回调的触发时机为当前Step所有ItemStream组件(包括ItemReader)执行完close()之后,完全不存在时序偏差问题。
自定义清理监听器实现:
将监听器绑定到对应Step,而非Job层面:public class SourceFileCleanStepListener implements StepExecutionListener { private final Path sourceFilePath; public SourceFileCleanStepListener(Path sourceFilePath) { this.sourceFilePath = sourceFilePath; } @Override public ExitStatus afterStep(StepExecution stepExecution) { // 仅当Step执行状态为COMPLETED时触发删除 if (stepExecution.getStatus() == BatchStatus.COMPLETED) { try { Files.deleteIfExists(sourceFilePath); log.info("源文件删除成功,路径:{}", sourceFilePath); } catch (IOException e) { log.error("源文件删除失败", e); return ExitStatus.FAILED; } } else { log.warn("Step执行状态为{},跳过源文件删除", stepExecution.getStatus()); } return ExitStatus.COMPLETED; } }@Bean public Step jsonParseStep(JobRepository jobRepository, PlatformTransactionManager txManager, JsonItemReader<MyObj> jsonItemReader, ItemWriter<MyObj> myObjItemWriter) { return new StepBuilder("jsonParseStep", jobRepository) .<MyObj, MyObj>chunk(100, txManager) .reader(jsonItemReader) .writer(myObjItemWriter) .listener(new SourceFileCleanStepListener(Paths.get("/data/input/source.json"))) .build(); } - 第三步:升级Spring Batch版本至4.3.0以上
官方在该版本中修复了*JsonItemReader.close()未委托调用JsonObjectReader.close()*的历史缺陷,升级后框架会自动完成底层流的释放,无需额外编写兜底关闭逻辑。 - 兜底防锁校验
若升级后仍存在偶发文件锁问题,可在删除逻辑前增加短暂的句柄释放校验,避免直接硬删:// 最多等待3秒,每500ms检测一次文件锁状态 long waitEnd = System.currentTimeMillis() + 3000; while (System.currentTimeMillis() < waitEnd) { try (FileInputStream fis = new FileInputStream(sourceFilePath.toFile())) { // 能正常打开流说明句柄已释放 break; } catch (IOException e) { Thread.sleep(500); } } // 执行删除 Files.deleteIfExists(sourceFilePath);
内容的提问来源于stack exchange,提问作者chickenempanada
相关产品推荐
相关产品推荐

