Spring Batch中如何分离读取平面文件表头与内容并分别处理
处理Spring Batch中平面文件表头与内容分离的方案
嘿,我之前正好遇到过一模一样的需求,给你梳理几个实用的实现思路,应该能帮你搞定!
方案一:利用FlatFileItemReader的skippedLinesCallback处理表头
这其实就是你最初考虑的思路,咱们把它落地成具体代码:
1. 定义实体类
先把表头和内容对应的实体类写好:
// 表头实体类 public class Header { private String param1; private String param2; private String param3; private String param4; private String param5; // 构造器、getter/setter省略 } // 内容实体类 public class Content { private String field1; private String field2; // ... 共12个字段 // 构造器、getter/setter省略 }
2. 实现SkippedLinesCallback解析表头
这个回调会处理我们跳过的表头行,解析后把表头对象存到StepExecution的ExecutionContext里,后续的processor或writer就能拿到:
public class HeaderLineCallback implements SkippedLinesCallback { private StepExecution stepExecution; @Override public void handleLine(String line) throws IOException { // 按实际分隔符拆分表头行,这里示例用逗号 String[] headerFields = line.split(","); Header header = new Header( headerFields[0], headerFields[1], headerFields[2], headerFields[3], headerFields[4] ); // 把表头存入ExecutionContext stepExecution.getExecutionContext().put("batchHeader", header); } // 通过Setter注入StepExecution @BeforeStep public void saveStepExecution(StepExecution stepExecution) { this.stepExecution = stepExecution; } }
3. 配置FlatFileItemReader
在Batch配置类里,配置reader时指定跳过1行,并绑定咱们的回调:
@Bean public FlatFileItemReader<Content> contentItemReader(HeaderLineCallback headerLineCallback) { return new FlatFileItemReaderBuilder<Content>() .name("contentItemReader") .resource(new ClassPathResource("input-data.csv")) .linesToSkip(1) // 跳过表头行 .skippedLinesCallback(headerLineCallback) .delimited() .names("field1", "field2", ..., "field12") // 对应Content的12个字段 .targetType(Content.class) .build(); }
方案二:自定义ItemReader,先读表头再读内容
如果觉得回调方式不够灵活,咱们可以自己实现ItemReader,先读取第一行处理表头,再逐行读取内容:
public class HeaderAndContentItemReader implements ItemReader<Content>, StepExecutionListener { private FlatFileItemReader<Content> delegateReader; private BufferedReader reader; private boolean headerProcessed = false; private StepExecution stepExecution; public HeaderAndContentItemReader(Resource resource) { this.delegateReader = new FlatFileItemReaderBuilder<Content>() .name("delegateContentReader") .resource(resource) .delimited() .names("field1", "field2", ..., "field12") .targetType(Content.class) .build(); } @Override public Content read() throws Exception { if (!headerProcessed) { // 先读取表头行 reader = new BufferedReader(new InputStreamReader(delegateReader.getResource().getInputStream())); String headerLine = reader.readLine(); String[] headerFields = headerLine.split(","); Header header = new Header(headerFields[0], headerFields[1], headerFields[2], headerFields[3], headerFields[4]); stepExecution.getExecutionContext().put("batchHeader", header); headerProcessed = true; // 把reader传递给代理,让它从下一行开始读 delegateReader.setReader(reader); delegateReader.open(stepExecution.getExecutionContext()); } return delegateReader.read(); } @Override public void beforeStep(StepExecution stepExecution) { this.stepExecution = stepExecution; } @Override public ExitStatus afterStep(StepExecution stepExecution) { return ExitStatus.COMPLETED; } }
然后在配置类里注册这个自定义reader:
@Bean public ItemReader<Content> customHeaderContentReader() { return new HeaderAndContentItemReader(new ClassPathResource("input-data.csv")); }
写入:将表头+所有内容输出到单个文件
接下来是写入环节,咱们需要先写表头,再写所有内容。可以通过自定义FlatFileItemWriter的beforeWrite方法实现:
public class HeaderAwareItemWriter extends FlatFileItemWriter<Content> implements StepExecutionListener { private Header batchHeader; @Override public void beforeWrite(List<? extends Content> items) { // 写入表头 if (batchHeader != null) { // 把Header对象转成对应字符串行,示例用逗号拼接 String headerLine = String.join(",", batchHeader.getParam1(), batchHeader.getParam2(), batchHeader.getParam3(), batchHeader.getParam4(), batchHeader.getParam5() ); try { getWriter().write(headerLine); getWriter().write(getLineSeparator()); } catch (IOException e) { throw new ItemWriterException("Failed to write header line", e); } } super.beforeWrite(items); } @Override public void beforeStep(StepExecution stepExecution) { // 从ExecutionContext取出之前存的表头 this.batchHeader = stepExecution.getExecutionContext().get("batchHeader", Header.class); } @Override public ExitStatus afterStep(StepExecution stepExecution) { return ExitStatus.COMPLETED; } }
配置这个writer:
@Bean public FlatFileItemWriter<Content> headerAwareWriter() { return new FlatFileItemWriterBuilder<Content>() .name("headerAwareWriter") .resource(new FileSystemResource("output-data.csv")) .delimited() .names("field1", "field2", ..., "field12") .listener(new HeaderAwareItemWriter()) .build(); }
注意事项
- 如果你的文件分隔符不是逗号,记得把所有
split(",")和delimited()的配置改成对应分隔符(比如制表符\t、竖线|)。 - 要处理异常场景,比如表头/内容行字段数量不匹配的情况,可以在回调或reader里加校验逻辑。
- 如果是多step场景,需要用
ExecutionContextPromotionListener把表头从step的ExecutionContext提升到job的ExecutionContext,实现跨step共享。
内容的提问来源于stack exchange,提问作者Fede Lopez
相关产品推荐
相关产品推荐

