Spring Batch仅二次运行才处理数据:SFTP下载文件读取异常求助
问题根源分析
你猜的没错!问题确实出在@Value("file:*.tsv")的加载时机上。Spring容器在初始化阶段就会解析这个注解,把对应的资源加载到inputResources里——这时候你的Step1(SFTP下载文件)还根本没执行,自然找不到刚要下载的文件。而第二次启动时,文件已经存在于目录中,容器初始化时就能读到,所以流程正常。
解决方案:延迟资源加载到Step执行时
要解决这个问题,我们需要让资源的扫描动作延迟到Step1执行完成、文件已经下载之后。最简洁的方式是使用Spring Batch的@StepScope注解,让MultiResourceItemReader的初始化延迟到Step2执行的时候。
修改步骤:
- 移除原来的
@Value("file:*.tsv") private Resource[] inputResources;,不需要提前注入资源了。 - 给
multiResourceItemReader和reader方法添加@StepScope注解,确保它们在Step执行时才初始化。 - 在
multiResourceItemReader中,通过ResourcePatternResolver动态扫描文件(此时Step1已经完成下载)。
修改后的代码如下:
@Configuration @EnableBatchProcessing public class BatchConfiguration { private static final Logger log = LoggerFactory.getLogger(BillProcessor.class); @Autowired public JobBuilderFactory jobBuilderFactory; @Autowired public StepBuilderFactory stepBuilderFactory; @Autowired private ResourcePatternResolver resourcePatternResolver; // 自动注入资源解析器 // ... 其他方法保持不变 ... @Bean @StepScope // 关键:延迟初始化到Step执行时 public FlatFileItemReader<Bill> reader() throws IOException, InterruptedException { log.info("start reader"); // 你的reader初始化逻辑 return reader; } @Bean @StepScope // 关键:延迟初始化到Step执行时 public MultiResourceItemReader<Bill> multiResourceItemReader() throws InterruptedException, IOException { log.info("start MultiResourceItemReader"); MultiResourceItemReader<Bill> resourceItemReader = new MultiResourceItemReader<Bill>(); // 此时Step1已经完成文件下载,动态扫描目录中的tsv文件 Resource[] resources = resourcePatternResolver.getResources("file:*.tsv"); resourceItemReader.setResources(resources); resourceItemReader.setDelegate(reader()); return resourceItemReader; } // ... writer和其他方法保持不变 ... }
为什么这样有效?
@StepScope会告诉Spring Batch,这个bean不需要在容器启动时创建,而是要等到对应的Step开始执行时才初始化。- 当Step2开始执行时,Step1已经完成了SFTP文件下载,此时调用
resourcePatternResolver.getResources("file:*.tsv")就能正确获取到刚下载的文件。
备选方案:使用StepExecutionListener
如果你不想用@StepScope,也可以通过Step执行监听器,在Step2启动前重新加载资源:
public class ResourceReloadListener implements StepExecutionListener { private final MultiResourceItemReader<Bill> multiResourceItemReader; private final ResourcePatternResolver resourcePatternResolver; public ResourceReloadListener(MultiResourceItemReader<Bill> multiResourceItemReader, ResourcePatternResolver resourcePatternResolver) { this.multiResourceItemReader = multiResourceItemReader; this.resourcePatternResolver = resourcePatternResolver; } @Override public void beforeStep(StepExecution stepExecution) { try { // Step2启动前重新扫描资源 Resource[] resources = resourcePatternResolver.getResources("file:*.tsv"); multiResourceItemReader.setResources(resources); } catch (IOException e) { throw new RuntimeException("Failed to reload input resources", e); } } @Override public ExitStatus afterStep(StepExecution stepExecution) { return null; } }
然后在Step2中注册这个监听器:
@Bean public Step step2() throws IOException, InterruptedException { return stepBuilderFactory.get("step2").<Bill, Bill>chunk(1000) .reader(multiResourceItemReader()) .writer(writer()) .listener(new ResourceReloadListener(multiResourceItemReader(), resourcePatternResolver)) .build(); }
这个方案也能实现同样的效果,但@StepScope的方式更简洁直观,推荐优先使用。
内容的提问来源于stack exchange,提问作者atalame
相关产品推荐
相关产品推荐

