You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Batch仅二次运行才处理数据:SFTP下载文件读取异常求助

问题根源分析

你猜的没错!问题确实出在@Value("file:*.tsv")的加载时机上。Spring容器在初始化阶段就会解析这个注解,把对应的资源加载到inputResources里——这时候你的Step1(SFTP下载文件)还根本没执行,自然找不到刚要下载的文件。而第二次启动时,文件已经存在于目录中,容器初始化时就能读到,所以流程正常。

解决方案:延迟资源加载到Step执行时

要解决这个问题,我们需要让资源的扫描动作延迟到Step1执行完成、文件已经下载之后。最简洁的方式是使用Spring Batch的@StepScope注解,让MultiResourceItemReader的初始化延迟到Step2执行的时候。

修改步骤:

  1. 移除原来的@Value("file:*.tsv") private Resource[] inputResources;,不需要提前注入资源了。
  2. 给multiResourceItemReader和reader方法添加@StepScope注解,确保它们在Step执行时才初始化。
  3. 在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:25:06