如何使用Jakarta XML Unmarshaller反序列化无命名空间XML
问题
尝试使用jakarta.xml.bind.Unmarshaller将XML反序列化为Java对象,但XML中缺少XSD定义的默认命名空间前缀映射(比如<test:one>未附带xmlns:test="https://test.com"),导致反序列化失败。要求在XMLStreamReader或Unmarshaller层面处理,不引入额外第三方库。
反序列化失败的XML示例
<foo> <bar>Hello</bar> <test:one>Hello ONE</test:one> </foo>
可正常反序列化的XML示例(补充命名空间后)
<foo> <bar>Hello</bar> <test:one xmlns:test="https://test.com">Hello ONE</test:one> </foo>
报错信息
Exception in thread "main" jakarta.xml.bind.UnmarshalException - with linked exception: [Exception [EclipseLink-25004] (Eclipse Persistence Services - 4.0.2.v202306161219): org.eclipse.persistence.exceptions.XMLMarshalException Exception Description: An error occurred unmarshalling the document Internal Exception: javax.xml.stream.XMLStreamException: ParseError at [row,col]:[3,15] Message: http://www.w3.org/TR/1999/REC-xml-names-19990114#ElementPrefixUnbound?test&test:one] at org.eclipse.persistence.jaxb.JAXBUnmarshaller.handleXMLMarshalException(JAXBUnmarshaller.java:1132) at org.eclipse.persistence.jaxb.JAXBUnmarshaller.unmarshal(JAXBUnmarshaller.java:471) at io.convert.test.Main.main(Main.java:24) Caused by: Exception [EclipseLink-25004] (Eclipse Persistence Services - 4.0.2.v202306161219): org.eclipse.persistence.exceptions.XMLMarshalException Exception Description: An error occurred unmarshalling the document
已尝试但无效的方法
- 自定义
NamespaceIgnoringXMLStreamReader继承StreamReaderDelegate,重写getAttributeNamespace和getNamespaceURI返回空字符串 - 添加
package-info.java设置@XmlSchema(elementFormDefault = XmlNsForm.UNQUALIFIED)
相关代码
Main.class
import jakarta.xml.bind.JAXBContext; import jakarta.xml.bind.JAXBException; import jakarta.xml.bind.Unmarshaller; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import java.io.InputStream; public class Main { public static void main(String[] args) throws XMLStreamException, JAXBException { final XMLInputFactory inputFactory = XMLInputFactory.newInstance(); inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE); inputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE); final InputStream inputStream = Main.class.getResourceAsStream("/SampleXML.xml"); final XMLStreamReader xmlStreamReader = inputFactory.createXMLStreamReader(inputStream); final JAXBContext jaxbContext = JAXBContext.newInstance("io.convert.test", Thread.currentThread().getContextClassLoader()); final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); xmlStreamReader.next(); while (xmlStreamReader.hasNext()) { Object event = unmarshaller.unmarshal(xmlStreamReader, Foo.class).getValue(); System.out.println(event.toString()); } } }
Foo.class
import jakarta.xml.bind.annotation.*; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; import java.util.List; @Data @NoArgsConstructor @AllArgsConstructor @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "foo") @XmlType( name = "Foo", propOrder = { "bar", "anyElements" }, factoryClass = ObjectFactory.class, factoryMethod = "createFoo") @ToString(callSuper = true) public class Foo { private String bar; @XmlAnyElement(lax = true) private List<Object> anyElements; void beforeUnmarshal(Unmarshaller unmarshaller, Object parent) { System.out.println("Before Unmarshaller Callback"); System.out.println(parent); } void afterUnmarshal(Unmarshaller unmarshaller, Object parent) { System.out.println("Before Unmarshaller Callback"); System.out.println(parent); } }
ObjectFactory.class
import jakarta.xml.bind.annotation.XmlRegistry; @XmlRegistry public final class ObjectFactory { private ObjectFactory() {} public static Foo createFoo() { return new Foo(); } }
解决方案
方案1:自定义XMLStreamReader补充缺失的命名空间前缀映射
问题核心是XML使用了未绑定的前缀test,解析器直接抛出错误。我们可以自定义StreamReaderDelegate,在遇到未绑定的前缀时返回预设的命名空间URI:
import javax.xml.stream.XMLStreamReader; import javax.xml.stream.util.StreamReaderDelegate; import java.util.HashMap; import java.util.Map; public class NamespaceInjectingXMLStreamReader extends StreamReaderDelegate { // 预设XSD中定义的前缀与命名空间映射 private static final Map<String, String> PREFIX_TO_NS = new HashMap<>(); static { PREFIX_TO_NS.put("test", "https://test.com"); } public NamespaceInjectingXMLStreamReader(XMLStreamReader reader) { super(reader); } // 查询前缀对应的命名空间时,返回预设值 @Override public String getNamespaceURI(String prefix) { String ns = PREFIX_TO_NS.get(prefix); if (ns != null) { return ns; } return super.getNamespaceURI(prefix); } // 处理元素自身的命名空间URI @Override public String getNamespaceURI() { String prefix = getPrefix(); if (prefix != null && PREFIX_TO_NS.containsKey(prefix)) { return PREFIX_TO_NS.get(prefix); } return super.getNamespaceURI(); } }
在Main类中替换原XMLStreamReader:
final XMLStreamReader xmlStreamReader = inputFactory.createXMLStreamReader(inputStream); NamespaceInjectingXMLStreamReader injectingReader = new NamespaceInjectingXMLStreamReader(xmlStreamReader); // 后续使用injectingReader进行反序列化 final JAXBContext jaxbContext = JAXBContext.newInstance("io.convert.test", Thread.currentThread().getContextClassLoader()); final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); injectingReader.next(); while (injectingReader.hasNext()) { Object event = unmarshaller.unmarshal(injectingReader, Foo.class).getValue(); System.out.println(event.toString()); }
方案2:禁用XML命名空间检查(不推荐)
部分XML输入工厂支持禁用命名空间处理,但会丢失所有命名空间信息,仅适用于不需要区分命名空间的场景:
inputFactory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, Boolean.FALSE);
注意:此方式会导致JAXB无法识别带前缀元素的对应命名空间,可能使anyElements无法正确反序列化为目标类型。
方案3:在package-info中声明命名空间前缀映射
在io.convert.test包下创建package-info.java,明确前缀与命名空间的映射,配合方案1的自定义Reader使用:
@XmlSchema( namespace = "", elementFormDefault = XmlNsForm.UNQUALIFIED, xmlns = { @XmlNs(prefix = "test", namespaceURI = "https://test.com") } ) package io.convert.test; import jakarta.xml.bind.annotation.XmlNs; import jakarta.xml.bind.annotation.XmlNsForm; import jakarta.xml.bind.annotation.XmlSchema;
内容的提问来源于stack exchange,提问作者BATMAN_2008
相关产品推荐
相关产品推荐

