JAXB序列化含org.w3c.dom.Element对象时XML格式化异常求助
解决JAXB序列化DOM Element时的格式化异常问题
问题根源
JAXB开启JAXB_FORMATTED_OUTPUT后,只会对自身映射的POJO字段应用格式化规则,对于org.w3c.dom.Element这类原生DOM节点,会直接输出其原始节点内容,不会同步全局的缩进、换行配置——这就是为什么单独用Transformer处理DOM格式正常,但嵌入MxML序列化后出现格式错乱的原因。
可行解决方案
方案1:修改XmlAdapter,提前格式化DOM节点
调整你的PureXmlElementAdapter,在marshal阶段将DOM Element按照JAXB的格式化规则预格式化,再返回处理后的节点,确保JAXB输出时直接使用已对齐的内容。
代码示例:
public class PureXmlElementAdapter extends XmlAdapter<Object, Element> { @Override public Element marshal(Element element) throws Exception { // 匹配JAXB默认格式化的2空格缩进,可根据实际调整 TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); // 移除XML声明,避免和JAXB全局输出重复 transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); // 将DOM转为格式化后的XML字符串 StringWriter sw = new StringWriter(); transformer.transform(new DOMSource(element), new StreamResult(sw)); String formattedXml = sw.toString(); // 将格式化后的字符串重新解析为DOM节点,JAXB输出时会保留格式 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); Document doc = dbf.newDocumentBuilder().parse(new InputSource(new StringReader(formattedXml))); return doc.getDocumentElement(); } // 实现unmarshal方法(略) }
方案2:传递JAXB格式化参数到Adapter
如果需要动态匹配JAXB的缩进配置,可以给Adapter添加参数传递逻辑,在创建Marshaller时同步缩进量:
- 改造Adapter:
public class PureXmlElementAdapter extends XmlAdapter<Object, Element> { private int indentAmount = 0; public void setIndentAmount(int indentAmount) { this.indentAmount = indentAmount; } @Override public Element marshal(Element element) throws Exception { if (indentAmount <= 0) { return element; } TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(indentAmount)); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); StringWriter sw = new StringWriter(); transformer.transform(new DOMSource(element), new StreamResult(sw)); String formattedXml = sw.toString(); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); Document doc = dbf.newDocumentBuilder().parse(new InputSource(new StringReader(formattedXml))); return doc.getDocumentElement(); } // 实现unmarshal方法(略) }
- 创建Marshaller时配置Adapter:
JAXBContext context = JAXBContext.newInstance(MxML.class); Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); // 同步JAXB默认的2空格缩进,可根据实际需求调整 PureXmlElementAdapter adapter = new PureXmlElementAdapter(); adapter.setIndentAmount(2); marshaller.setAdapter(PureXmlElementAdapter.class, adapter); // 执行序列化 marshaller.marshal(mxML, System.out);
方案3:将DOM转为String类型处理
如果DOM节点的结构相对简单,可以直接在BusinessObjectWrapper中将Element字段改为String类型,序列化前用Transformer格式化DOM为字符串,JAXB会直接输出该字符串(需注意避免XML转义问题,可配合@XmlCDATA或确保字符串是合法XML片段)。
注意事项
- 必须移除Transformer输出的XML声明,否则会和JAXB全局输出的声明重复,导致XML格式错误。
- 确保Transformer的命名空间配置和JAXB POJO一致,避免序列化时出现命名空间错乱。
- 若使用自定义缩进字符(如制表符),需同步调整Transformer的
indent-amount或对应的属性。
内容的提问来源于stack exchange,提问作者Matteo NNZ
相关产品推荐
相关产品推荐

