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实例。
- 实现分类器逻辑
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(); } }
- 配置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中通过以下方式获取:
- 实现
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; } }
- 或者通过
@Value注入StepExecution:
@StepScope public class MyWriter implements ItemWriter<MyItem> { @Value("#{stepExecution.jobExecution.executionContext}") private ExecutionContext executionContext; // ... write方法实现 }
内容的提问来源于stack exchange,提问作者zsp
相关产品推荐
相关产品推荐

