多个基于XMLStreamWriter的Jackson XML自定义序列化器抛出异常
这个问题的核心在于你直接操作底层XMLStreamWriter时,破坏了Jackson XML生成器(ToXmlGenerator)的内部状态一致性,导致序列化流程的状态错乱。让我一步步拆解原因和解决办法:
为什么会抛出异常?
当Jackson序列化TestJacksonXml1这类Bean时,XmlBeanSerializer会按固定流程处理每个属性:
- 先调用
ToXmlGenerator.writeFieldName()输出属性对应的XML外层元素(比如<obj1>); - 再调用你的自定义序列化器处理属性值。
但你的自定义序列化器里,直接用XMLStreamWriter写了完整的独立元素(比如<class1>10</class1>),这就造成了两个关键冲突:
ToXmlGenerator已经写入了<obj1>的开始标签,而你的序列化器又额外插入了一套完整的元素结构,导致XML流的实际状态和ToXmlGenerator的内部记录完全不符;- 当第一个序列化器执行完毕,
ToXmlGenerator还以为自己处于<obj1>元素内部,接下来处理obj2时会尝试写入<obj2>的字段名,此时XML流的状态已经混乱,直接触发"期望值却写了字段名"的异常。
而只保留一个序列化器时,虽然状态依然不对,但因为没有后续属性的校验,问题被暂时隐藏了——本质上这种写法也是不符合Jackson XML模块设计的。
两种可行的解决方案
方案1:调整序列化逻辑,配合ToXmlGenerator的状态
既然ToXmlGenerator已经帮你处理了属性的外层元素(比如<obj1>),你的自定义序列化器只需要负责写入该元素内部的内容即可,不要自己写完整的独立元素:
修改CustomClass1Serializer的serialize方法:
@Override public void serialize(CustomClass1 customClass1, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { final ToXmlGenerator toXmlGenerator = (ToXmlGenerator) jsonGenerator; final XMLStreamWriter staxWriter = toXmlGenerator.getStaxWriter(); try { // 只写<obj1>内部的内容,不要写外层独立元素 staxWriter.writeStartElement("class1"); staxWriter.writeCharacters(String.valueOf(customClass1.prop1)); staxWriter.writeEndElement(); } catch (XMLStreamException e){ throw new IOException(e); // 不要仅打印异常,抛出给Jackson统一处理 } }
同理修改CustomClass2Serializer后,ToXmlGenerator的内部状态会和XML流保持一致:先写<obj1>,序列化器在内部写入<class1>10</class1>,然后ToXmlGenerator自动补全</obj1>,后续处理obj2时状态完全正常。
方案2:使用Jackson XML专属的XmlSerializer(更推荐)
Jackson XML模块提供了XmlSerializer类,它专门适配XML序列化的状态管理,能从根源避免状态不一致问题。你可以继承它实现自定义逻辑:
以CustomClass1Serializer为例:
static class CustomClass1Serializer extends XmlSerializer<CustomClass1> { @Override public void serialize(CustomClass1 value, XmlGenerator gen, SerializerProvider provider) throws IOException { XMLStreamWriter staxWriter = gen.getStaxWriter(); try { staxWriter.writeStartElement("class1"); staxWriter.writeCharacters(String.valueOf(value.prop1)); staxWriter.writeEndElement(); } catch (XMLStreamException e) { throw new IOException(e); } } // 重写isEmpty方法,告诉Jackson对象是否为空 @Override public boolean isEmpty(SerializerProvider provider, CustomClass1 value) { return value == null; } }
同时在TestJacksonXml1的属性上通过@JacksonXmlProperty指定外层元素名,确保序列化结构符合预期:
public class TestJacksonXml1 { @JsonSerialize(using = CustomClass1.CustomClass1Serializer.class) @JacksonXmlProperty(localName = "obj1") CustomClass1 obj1; @JsonSerialize(using = CustomClass2.CustomClass2Serializer.class) @JacksonXmlProperty(localName = "obj2") CustomClass2 obj2; // 构造方法、getter方法保持不变 }
这种写法完全遵循Jackson XML模块的设计规范,状态管理更可靠,也更适合复杂的XML嵌套需求。
为什么用JsonGenerator就没问题?
当你使用JsonGenerator的标准方法(比如writeString、writeObject)时,它会自动同步内部状态和底层XMLStreamWriter的操作,不会出现状态脱节的情况。而直接操作XMLStreamWriter相当于绕开了Jackson的状态管理机制,自然容易引发异常。
内容的提问来源于stack exchange,提问作者mac

