使用Faster Jackson反序列化数组时限制最大元素数的实现方案咨询
Jackson 反序列化时实时限制数组大小、避免超大数组触发OOM的实现
要实现反序列化过程中的数组大小限制,核心是依托Jackson本身的流式解析能力,不要等到全量Java对象生成后再做校验,从根源上减少无效内存占用,具体有两种实现路径,按改动量从低到高选择即可。
方案一:Jackson 2.15+ 版本内置的流层限制(最简便)
如果项目使用的Jackson版本在2.15及以上,不需要写任何自定义逻辑,直接在构建ObjectMapper时配置流读取约束即可,这个限制是在最底层的JSON字节流解析阶段生效的,内存效率最高。
配置示例:
JsonFactory jsonFactory = JsonFactory.builder() .streamReadConstraints( StreamReadConstraints.builder() // 设置单个JSON数组最多允许解析的元素数量,超过立刻抛原生解析异常 .maxArrayLength(100) .build() ) .build(); ObjectMapper objectMapper = new ObjectMapper(jsonFactory);
- 限制生效时机:解析器每读到一个数组元素就会计数,一旦计数超过阈值,立刻停止读取后续的JSON字节,抛出
JsonParseException,根本不会把超出限制的内容解析成Java对象,从流程上避免大数组占满堆内存。 - 优势:全局统一配置,不需要针对每个自定义类写适配代码,异常类型和Jackson原生异常完全统一,不需要额外的异常处理逻辑。
方案二:自定义列表反序列化器(适配低版本Jackson)
如果项目使用的Jackson版本低于2.15,只需要针对需要限制大小的列表类型写一个轻量的自定义反序列化器,在解析数组元素的过程中实时计数,超过阈值直接终止解析即可,对原有业务代码侵入性极低。
- 实现自定义的列表反序列化器,逐Token遍历数组元素、实时计数:
public class LimitedSizeActionListDeserializer extends StdDeserializer<List<Action>> { // 自定义允许的最大数组元素数量 private static final int MAX_ALLOWED_ACTION_COUNT = 100; public LimitedSizeActionListDeserializer() { super(List.class); } @Override public List<Action> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { List<Action> result = new ArrayList<>(MAX_ALLOWED_ACTION_COUNT); // 跳过数组起始标记 p.nextToken(); int currentCount = 0; // 逐元素遍历直到数组结束标记 while (p.currentToken() != JsonToken.END_ARRAY) { currentCount++; // 计数超过阈值直接抛异常,不再解析后续元素 if (currentCount > MAX_ALLOWED_ACTION_COUNT) { throw ctxt.weirdStringException( p.currentName(), Action.class, String.format("actions数组元素数量超出限制,最多允许%d个元素", MAX_ALLOWED_ACTION_COUNT) ); } // 正常解析当前元素加入列表 Action action = p.readValueAs(Action.class); result.add(action); p.nextToken(); } return result; } } - 在原有
InputMessage类的构造参数上,通过注解指定使用这个自定义反序列化器即可,其余业务逻辑完全不需要改动:class InputMessage { private final List<Action> actions; @JsonCreator public InputMessage( @JsonProperty("actions") @JsonDeserialize(using = LimitedSizeActionListDeserializer.class) final List<Action> actions ) { this.actions = actions; } public List<Action> getActions() { return actions; } }
注意:实现自定义反序列化器时一定要逐Token遍历计数,不要直接调用
readValueAsList()这类全量加载的方法,否则会退回到先全量加载再校验的老逻辑,完全失去防OOM的作用。
避坑说明
- 不要采用“全量反序列化完成后在构造方法中检查列表大小”的方案:这种逻辑会先把整个超大数组全部解析成Java对象、加载到堆内存之后才做判断,哪怕后续抛异常,之前加载的海量对象已经占用了大量内存,无法达到防止OOM的目的。
内容的提问来源于stack exchange,提问作者Eric
相关产品推荐
相关产品推荐

