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

Spring Batch:如何在FlatFileItemWriter中读取Item属性生成文件名

问题描述

我需要生成符合特定命名规则的文件,但文件名依赖于MyItem类的属性,请问如何让FlatFileItemWriter能够读取待写入文件的Item的属性?

我在配置类中创建了如下ItemWriter<MyItem> Bean:

@Bean
public ItemWriter<MyItem> outputFileWriter() {
    DateTimeFormatter dateDefaultFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
    DateTimeFormatter hourDefaultFormatter = DateTimeFormatter.ofPattern("HHmmss");
    
    String outputFile = "arq33" + MyEnum.valueOf(((String) executionContext.get("myProperty")).substring(4, 6)) 
                                     + dateDefaultFormatter.format(LocalDate.now()) 
                                     + hourDefaultFormatter.format(LocalDateTime.now());
            
    Resource exportFileResource = new FileSystemResource(folderOutput + outputFile); 
      
    return new FlatFileItemWriterBuilder<MyItem>()
            .name("outputFileWriter")
            .lineAggregator(new MyFileLineAggregator(outputFile))
            .resource(exportFileResource)
            .build();
}

已尝试的方案

  • 使用executionContext但获取到null(已在配置类中注入executionContext):
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {

@Value("#{jobExecution.executionContext}")
    private ExecutionContext executionContext;
   ...
}
  • 尝试自定义FlatFileItemWriter相关类,但不确定是否可行,代码如下:
@StepScope
public class OutputFileWriter extends StepExecutionListenerSupport {
        
    @Value("${folder.output}")
    private String folderOutput;
    
    @Value("#{jobParameters['myProperty']}")
    private String test;
    
    DateTimeFormatter dateDefaultFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
    DateTimeFormatter hourDefaultFormatter = DateTimeFormatter.ofPattern("HHmmss");
    
    String outputFile = "arq33" + MyEnum.valueOf(test.substring(4, 6)) 
                                     + dateDefaultFormatter.format(LocalDate.now()) 
                                     + hourDefaultFormatter.format(LocalDateTime.now());
    
    private Resource exportFileResource = new FileSystemResource(folderOutput + outputFile);
         
    public FlatFileItemWriter<MyItem> writer() {
        FlatFileItemWriter<MyItem> writer = new FlatFileItemWriter<MyItem>();
        writer.setResource(exportFileResource);
         
        writer.setAppendAllowed(true);
         
        writer.setLineAggregator(new MyFileLineAggregator(outputFile));
        
        return writer;
    }
}
解决方案

方案一:按Item属性路由到不同文件(ClassifierCompositeItemWriter)

如果需要根据每个MyItem的属性值,将Item写入对应命名的文件,推荐使用Spring Batch提供的ClassifierCompositeItemWriter,它可以根据自定义分类逻辑,把不同Item路由到对应的FlatFileItemWriter实例。

  1. 实现分类器逻辑
public class MyItemClassifier implements Classifier<MyItem, ItemWriter<? super MyItem>> {
    private final String folderOutput;
    // 缓存已创建的Writer,避免重复初始化
    private final Map<String, ItemWriter<MyItem>> writerCache = new ConcurrentHashMap<>();

    public MyItemClassifier(String folderOutput) {
        this.folderOutput = folderOutput;
    }

    @Override
    public ItemWriter<? super MyItem> classify(MyItem item) {
        // 从Item中提取用于生成文件名的属性
        String propertyValue = item.getMyProperty();
        String key = propertyValue.substring(4, 6);
        // 不存在则创建新的Writer
        return writerCache.computeIfAbsent(key, this::createFileWriter);
    }

    private FlatFileItemWriter<MyItem> createFileWriter(String key) {
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        DateTimeFormatter hourFormatter = DateTimeFormatter.ofPattern("HHmmss");
        String fileName = "arq33" + MyEnum.valueOf(key) 
                + dateFormatter.format(LocalDate.now()) 
                + hourFormatter.format(LocalDateTime.now());
        Resource resource = new FileSystemResource(folderOutput + fileName);
        
        return new FlatFileItemWriterBuilder<MyItem>()
                .name("item-writer-" + key)
                .lineAggregator(new MyFileLineAggregator(fileName))
                .resource(resource)
                .appendAllowed(true)
                .build();
    }
}
  1. 配置Composite Writer
@Bean
@StepScope
public ClassifierCompositeItemWriter<MyItem> compositeItemWriter(@Value("${folder.output}") String folderOutput) {
    ClassifierCompositeItemWriter<MyItem> compositeWriter = new ClassifierCompositeItemWriter<>();
    compositeWriter.setClassifier(new MyItemClassifier(folderOutput));
    return compositeWriter;
}

方案二:动态生成单一文件(自定义ItemWriter)

如果所有Item最终写入同一个文件,且文件名需要从第一个Item的属性获取,可以自定义ItemWriter,在首次写入时初始化FlatFileItemWriter:

@StepScope
public class DynamicFileItemWriter implements ItemWriter<MyItem>, StepExecutionListener {
    private final String folderOutput;
    private FlatFileItemWriter<MyItem> delegateWriter;

    public DynamicFileItemWriter(@Value("${folder.output}") String folderOutput) {
        this.folderOutput = folderOutput;
    }

    @Override
    public void write(List<? extends MyItem> items) throws Exception {
        if (delegateWriter == null) {
            // 从第一个Item提取属性生成文件名
            MyItem firstItem = items.get(0);
            String propertyValue = firstItem.getMyProperty();
            String key = propertyValue.substring(4, 6);
            
            DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyyMMdd");
            DateTimeFormatter hourFormatter = DateTimeFormatter.ofPattern("HHmmss");
            String fileName = "arq33" + MyEnum.valueOf(key) 
                    + dateFormatter.format(LocalDate.now()) 
                    + hourFormatter.format(LocalDateTime.now());
            Resource resource = new FileSystemResource(folderOutput + fileName);
            
            delegateWriter = new FlatFileItemWriterBuilder<MyItem>()
                    .name("dynamic-file-writer")
                    .lineAggregator(new MyFileLineAggregator(fileName))
                    .resource(resource)
                    .build();
            delegateWriter.open(new ExecutionContext());
        }
        delegateWriter.write(items);
    }

    @Override
    public void beforeStep(StepExecution stepExecution) {
        // 可选:前置初始化操作
    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        // 关闭Writer资源
        if (delegateWriter != null) {
            try {
                delegateWriter.close();
            } catch (Exception e) {
                throw new RuntimeException("关闭文件写入器失败", e);
            }
        }
        return ExitStatus.COMPLETED;
    }
}

配置自定义Writer:

@Bean
@StepScope
public ItemWriter<MyItem> dynamicFileItemWriter(@Value("${folder.output}") String folderOutput) {
    return new DynamicFileItemWriter(folderOutput);
}

关于ExecutionContext获取null的问题

直接在配置类中注入ExecutionContext会返回null,因为配置类初始化时JobExecution尚未创建。正确做法是在StepScope的Bean中通过以下方式获取:

  1. 实现StepExecutionListener接口,在beforeStep方法中获取:
@StepScope
public class MyStepAwareWriter implements ItemWriter<MyItem>, StepExecutionListener {
    private ExecutionContext executionContext;

    @Override
    public void beforeStep(StepExecution stepExecution) {
        this.executionContext = stepExecution.getJobExecution().getExecutionContext();
        // 这里可以读取executionContext中的属性
    }

    @Override
    public void write(List<? extends MyItem> items) throws Exception {
        // 业务逻辑
    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        return ExitStatus.COMPLETED;
    }
}
  1. 或者通过@Value注入StepExecution:
@StepScope
public class MyWriter implements ItemWriter<MyItem> {
    @Value("#{stepExecution.jobExecution.executionContext}")
    private ExecutionContext executionContext;

    // ... write方法实现
}

内容的提问来源于stack exchange,提问作者zsp

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 02:12:09