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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 10:18:36