tl; dr:将输入验证添加到Jackson中的自定义json解串器很重要。
在RHQ中,我们在几个地方使用了Json解析-直接在as7 / Wildfly插件中,或者通过RESTEasy 2.3.5间接在REST-api中使用,已经很繁重了。
现在,我们有一个bean Link
,看起来像:
public class Link {String rel;String href;
}
序列化的标准方法是
{ "rel":"edit", "href":"http://acme.org" }
由于我们需要其他格式,因此我编写了一个自定义序列化程序并将其附加到类上。
@JsonSerialize(using = LinkSerializer.class)
@JsonDeserialize(using = LinkDeserializer.class)
@Produces({"application/json","application/xml"})
public class Link {private String rel;private String href;
此自定义格式如下:
{"edit": {"href": "http://acme.org"}
}
由于客户端也可以发送链接,因此需要进行一些自定义反序列化。 解串器的第一个片段看起来像这样,效果很好:
public class LinkDeserializer extends JsonDeserializer>{@Overridepublic Link deserialize(JsonParser jp,DeserializationContext ctxt) throws IOException{String tmp = jp.getText(); // {jp.nextToken(); // skip over { to the relString rel = jp.getText();jp.nextToken(); // skip over {[…]Link link = new Link(rel,href);return link;}
现在,几天前发生的事情是,在某些测试中,我正在发送数据,而我们的服务器严重崩溃。 内存使用量增加,垃圾收集器花费了大量cpu时间,并且该调用最终因OutOfMemoryException
终止。
经过一番调查,我发现客户端不是以我们的特殊格式发送Link
对象,而是以我最初显示的原始格式发送。 进一步的研究表明,实际上, LinkDeserializer
正在消耗流中的令牌,如上所示,然后还吞没了输入中的后续令牌。 因此,当它返回时,整个解析器的状态很差,然后尝试复制大数组,直到我们看到OOME。
得到这个之后,我更改了实现以添加验证并在无效输入时尽早提供援助,以使解析器在无效输入时不会陷入不良状态:
public Link deserialize(JsonParser jp,DeserializationContext ctxt) throws IOException{String tmp = jp.getText(); // {validate(jp, tmp,"{");jp.nextToken(); // skip over { to the relString rel = jp.getText();validateText(jp, rel);jp.nextToken(); // skip over {tmp = jp.getText();validate(jp, tmp,"{");[…]
然后,那些validate*()
简单地将令牌与传递的期望值进行比较,并对意外输入抛出Exception:
private void validate(JsonParser jsonParser, String input,String expected) throws JsonProcessingException {if (!input.equals(expected)) {throw new JsonParseException("Unexpected token: " + input,jsonParser.getTokenLocation());}}
验证也许可以进一步改进,但是您可以理解。
翻译自: https://www.javacodegeeks.com/2013/08/custom-deserializer-in-jackson-and-validation.html