Spring Batch DelimitedLineTokenizer如何禁用双引号反转义
解决方案
org.springframework.batch.item.file.transform.DelimitedLineTokenizer 默认将字段内连续双引号替换为单引号的反转义逻辑是硬编码在分词流程中的,网上常见的重写isQuoteCharacter返回false的方案会直接让分词器失去识别双引号包裹字段边界的能力,自然无法支持跨多行字段的业务场景,完全不适用。
可以通过自定义分词子类的方式实现需求,既保留多行字段解析能力,又关闭双引号反转义:
- 自定义类继承
DelimitedLineTokenizer,重写分词核心方法,保留原有的引号包裹判断逻辑以支持多行字段,仅跳过连续双引号转单引号的替换步骤,核心实现参考:
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer; import java.util.ArrayList; import java.util.List; public class RawContentDelimitedLineTokenizer extends DelimitedLineTokenizer { @Override protected List<String> doTokenize(String line) { List<String> tokens = new ArrayList<>(); int currentPos = 0; int lineLen = line.length(); boolean inQuotedField = false; char delimiter = getDelimiter().charAt(0); char quoteChar = getQuoteCharacter(); for (int i = 0; i < lineLen; i++) { char current = line.charAt(i); if (current == quoteChar) { if (inQuotedField) { // 识别到连续双引号时,直接跳过,不做转义替换 if (i + 1 < lineLen && line.charAt(i + 1) == quoteChar) { i++; continue; } inQuotedField = false; } else { inQuotedField = true; currentPos = i + 1; continue; } } // 仅在非引号包裹区域遇到分隔符时切分字段,保留多行字段解析能力 if (current == delimiter && !inQuotedField) { tokens.add(extractRawToken(line, currentPos, i)); currentPos = i + 1; } } // 处理行尾最后一个字段 if (currentPos <= lineLen) { tokens.add(extractRawToken(line, currentPos, lineLen)); } return tokens; } /** * 复用原类的字段裁剪规则:按需去除首尾空白、裁掉字段外层包裹的双引号,保留字段内部原始内容 */ private String extractRawToken(String line, int start, int end) { int trimStart = start; int trimEnd = end; if (isTrimTokens()) { while (trimStart < trimEnd && Character.isWhitespace(line.charAt(trimStart))) { trimStart++; } while (trimEnd > trimStart && Character.isWhitespace(line.charAt(trimEnd - 1))) { trimEnd--; } } if (trimEnd - trimStart >= 2 && line.charAt(trimStart) == getQuoteCharacter() && line.charAt(trimEnd - 1) == getQuoteCharacter()) { trimStart++; trimEnd--; } return line.substring(trimStart, trimEnd); } }
- 修改原有Spring XML配置,将原本内置的
DelimitedLineTokenizer替换为你自定义的这个子类即可,其余delimiter、names、fieldSetMapper等配置完全不需要改动,兼容原有业务逻辑:
<bean class="org.springframework.batch.item.file.mapping.DefaultLineMapper"> <property name="lineTokenizer"> <bean class="com.your.package.RawContentDelimitedLineTokenizer"> <property name="delimiter" ref="delimiter-#{jobExecutionContext['DELIMITER']}" /> <property name="names" value="#{jobExecutionContext['COLUMNS_NAME_LOOKUP']}" /> </bean> </property> <property name="fieldSetMapper"> <bean class="com.tsys.enterprise.converters.flexible.delimited.file.FlexibleDelimiterBasedFileParserFieldSetMapper" scope="step"> <aop:scoped-proxy/> <property name="targetType" value="com.tsys.enterprise.converters.flexible.delimited.file.vo.DataHolder"/> <property name="udfLabel1Label" value="#{jobExecutionContext['UDF1_LABEL']}"/> </bean> </property> </bean>
方案说明
- 该实现完全保留了原生分词器对引号包裹字段的边界识别逻辑,引号包裹范围内的分隔符、换行符都不会被当成切分标记,跨多行字段的解析不受任何影响
- 仅移除了原实现中字段值内
""替换为"的反转义逻辑,输出的字段内容和原始文件完全一致,下游二次调用DelimitedLineTokenizer解析时不会因为提前生成的单个双引号出现解析错误 - 如果使用的Spring Batch版本较老,
doTokenize方法逻辑有差异,可以直接对照对应版本的源码,找到执行双引号替换的代码段移除即可,不需要完全重写整个分词方法。
内容的提问来源于stack exchange,提问作者user9241912
相关产品推荐
相关产品推荐

