Spring Batch处理含数组的复杂DTO:如何拆分列表字段
Spring Batch 解析含数组字段的DTO方案
问题说明
你有如下DTO定义:
public class Dto { private String somethingObject; private List<String> arrayOfObjects; }
需要通过Spring Batch做批处理,输入文件每行格式为:
object | arrayItem1 arrayItem2 arrayItem3
使用|作为分隔符的LineTokenizer只能把arrayOfObjects识别成一个包含长字符串的列表,无法拆分成3个独立元素,需要解决这个解析问题。
解决办法
方法一:自定义FieldSetMapper(推荐,灵活适配)
在字段映射阶段手动拆分数组字段,是适用性最广的方案:
- 先配置
LineTokenizer按|拆分每行,得到两个基础字段:
@Bean public LineTokenizer lineTokenizer() { DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer(); tokenizer.setDelimiter("|"); tokenizer.setNames("somethingObject", "arrayField"); tokenizer.setStrict(false); // 防止字段数量不匹配时报错 return tokenizer; }
- 实现自定义
FieldSetMapper,将第二个字段按空格拆分成列表:
public class DtoFieldSetMapper implements FieldSetMapper<Dto> { @Override public Dto mapFieldSet(FieldSet fieldSet) throws BindException { Dto dto = new Dto(); // 去除首尾空格,避免空字符干扰 dto.setSomethingObject(fieldSet.readString("somethingObject").trim()); // 按任意数量空格拆分,自动过滤空字符串 String[] arrayItems = fieldSet.readString("arrayField").trim().split("\\s+"); dto.setArrayOfObjects(Arrays.asList(arrayItems)); return dto; } }
- 将自定义Mapper配置到
FlatFileItemReader中:
@Bean public FlatFileItemReader<Dto> itemReader() { return new FlatFileItemReaderBuilder<Dto>() .resource(new ClassPathResource("input.txt")) .lineTokenizer(lineTokenizer()) .fieldSetMapper(new DtoFieldSetMapper()) .build(); }
方法二:自定义LineTokenizer(适合固定长度数组)
如果想在拆分每行的阶段就完成数组项的拆分,可以扩展DelimitedLineTokenizer:
public class CustomLineTokenizer extends DelimitedLineTokenizer { @Override protected List<String> doTokenize(String line) { List<String> tokens = super.doTokenize(line); // 处理第二个字段,按空格拆分后替换原字段 if (tokens.size() >= 2) { String arrayStr = tokens.get(1).trim(); List<String> arrayTokens = Arrays.asList(arrayStr.split("\\s+")); tokens.remove(1); tokens.addAll(1, arrayTokens); } return tokens; } }
接着配置字段映射,把拆分后的每个数组项绑定到arrayOfObjects的对应位置:
@Bean public LineTokenizer customLineTokenizer() { CustomLineTokenizer tokenizer = new CustomLineTokenizer(); tokenizer.setDelimiter("|"); tokenizer.setNames("somethingObject", "arrayItem1", "arrayItem2", "arrayItem3"); return tokenizer; } @Bean public FieldSetMapper<Dto> fieldSetMapper() { BeanWrapperFieldSetMapper<Dto> mapper = new BeanWrapperFieldSetMapper<>(); mapper.setTargetType(Dto.class); mapper.setFieldMap(Map.of( "arrayItem1", "arrayOfObjects[0]", "arrayItem2", "arrayOfObjects[1]", "arrayItem3", "arrayOfObjects[2]" )); return mapper; }
这种方法仅适合数组元素数量固定的场景,元素数量不固定时优先选方法一。
方法三:调整输入文件格式(最简单,但需修改文件)
如果允许调整输入文件格式,把数组项用逗号(或其他无冲突分隔符)分开:
object | arrayItem1,arrayItem2,arrayItem3
然后编写字符串转List的转换器,注册到映射器中:
public class StringToListConverter implements Converter<String, List<String>> { @Override public List<String> convert(String source) { if (source == null || source.isBlank()) { return Collections.emptyList(); } return Arrays.asList(source.trim().split(",")); } }
配置映射器时添加该转换器:
@Bean public FieldSetMapper<Dto> fieldSetMapper() { BeanWrapperFieldSetMapper<Dto> mapper = new BeanWrapperFieldSetMapper<>(); mapper.setTargetType(Dto.class); DefaultConversionService conversionService = new DefaultConversionService(); conversionService.addConverter(new StringToListConverter()); mapper.setConversionService(conversionService); return mapper; }
这种方法最省事,但前提是你能修改输入文件的格式。
内容的提问来源于stack exchange,提问作者Bence Ágocsi Kiss
相关产品推荐
相关产品推荐

