You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 02:38:21