如何重构XSD以便解组不返回JAXBElement

时间:2016-09-16 09:38:09

标签: java xsd jaxb unmarshalling

我有以下架构:

<xsd:schema xmlns:bar="http://www.foo.org/bar"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
        xmlns:annox="http://annox.dev.java.net"
        xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
        targetNamespace="http://www.foo.org/bar"
        jaxb:extensionBindingPrefixes="annox" jaxb:version="2.1" elementFormDefault="qualified">

    <xsd:element name="unit" type="bar:unit" />

    <xsd:complexType name="unit">
        <xsd:annotation>
            <xsd:appinfo>
                <annox:annotate>@javax.xml.bind.annotation.XmlRootElement(name="unit")</annox:annotate>
            </xsd:appinfo>
        </xsd:annotation>
            <xsd:sequence>
            <xsd:any processContents="skip" />
        </xsd:sequence>
    </xsd:complexType>

</xsd:schema>

当我解组这个XML时

<unit xmlns="http://www.foo.org/bar">
    <text>Name</text>
</unit>

返回的对象是javax.xml.bind.JAXBElement<Unit>,但是我希望得到org.foo.bar.Unit。我需要这个,因为在我的情况下解组是由JAX-RS提供者或SpringWeb隐式发生的。

观察:

  • 如果我删除/替换<xsd:any processContents="skip" />声明,JAXB开始返回org.foo.bar.Unit
  • 如果我删除<xsd:element name="unit" type="bar:unit" />声明,JAXB开始返回org.foo.bar.Unit(虽然需要在解组时禁用验证)。

因此,我会说,鉴于XSD是证明问题的最小XSD。

问题:为什么JAXB将org.foo.bar.Unit包装到JAXBElement以上的组合?从我看到的情况来看,XSD类型unit无法使标记名称与unit不同,那么为什么JAXB需要这种工厂方法?

@XmlElementDecl(namespace = "http://www.foo.org/bar", name = "unit")
public JAXBElement<Unit> createUnit(Unit value) { ... }

证明JAXB 2.2.7问题的项目是here。运行时输出以下内容:

Running org.foo.bar.UnitTest
>>> Class is: javax.xml.bind.JAXBElement
>>> XML is: <?xml version="1.0" encoding="UTF-8" standalone="yes"?><unit xmlns="http://www.foo.org/bar"><text>Name</text></unit>
Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.318 sec <<< FAILURE!

5 个答案:

答案 0 :(得分:4)

添加到伊恩的答案, 由complex元素命名的任何root元素都将使用@XmlElementDecl()注释工厂方法。

您可以通过内联移动complex类型声明来解决此问题。

<xsd:schema xmlns= "http://www.foo.org/bar" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:annox="http://annox.dev.java.net" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
    targetNamespace="http://www.foo.org/bar" jaxb:extensionBindingPrefixes="annox"
    jaxb:version="2.1" elementFormDefault="qualified">
    <xsd:element name="unit">
        <xsd:complexType>
            <xsd:annotation>
                <xsd:appinfo>
                    <annox:annotate>@javax.xml.bind.annotation.XmlRootElement(name="unit")
                    </annox:annotate>
                </xsd:appinfo>
            </xsd:annotation>
            <xsd:sequence>
                <xsd:any processContents="skip" />
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

ObjectFactory.class (此处未生成JAXBElement工厂方法)

@XmlRegistry
public class ObjectFactory {


    /**
     * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: org.foo.bar
     * 
     */
    public ObjectFactory() {
    }

    /**
     * Create an instance of {@link Unit }
     * 
     */
    public Unit createUnit() {
        return new Unit();
    }

}

测试类:

@Test
public void testUnmarshalling() throws JAXBException, SAXException {
    JAXBContext context = JAXBContext.newInstance(Unit.class);

    Unmarshaller unmarshaller = context.createUnmarshaller();

    unmarshaller.setSchema(SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
            .newSchema(new StreamSource(getClass().getClassLoader().getResourceAsStream("common.xsd"))));

    Object unit = unmarshaller.unmarshal(getClass().getResourceAsStream("unit.xml"));

    System.out.println(">>> Class is: " + unit.getClass().getName());

    StringWriter writer = new StringWriter();
    context.createMarshaller().marshal(unit, writer);

    System.out.println(">>> XML is: " + writer.toString());

    //assertTrue(unit instanceof Unit);
}

测试xml:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<unit xmlns="http://www.foo.org/bar">
    <text>Name</text>
</unit>

输出

>>> Class is: org.foo.bar.Unit
>>> XML is: <?xml version="1.0" encoding="UTF-8" standalone="yes"?><unit xmlns="http://www.foo.org/bar"><text>Name</text></unit>

答案 1 :(得分:3)

  

从我看来,XSD类型单元无法使标签名称与单元不同,那么为什么JAXB需要这种工厂方法呢?

相反 - 当您的架构通过引用使用命名的复杂类型时,您将始终获得JAXBElement。对于命名的复杂类型,类型可能总是用于不同的元素(可能在另一个导入模式中),或者元素可能使用命名类型的子类型而不是顶层类型本身。 / p>

当全局xsd:element声明具有嵌套的匿名complexType时,将使用展开的根元素,因为在那种情况下,unmarshaller知道这些替换不会发生。

答案 2 :(得分:1)

如果您正在做这样的事情:

JAXBContext jaxbContext = JAXBContext.newInstance(Unit.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();

JAXBElement<Unit> root = jaxbUnmarshaller.unmarshal(new StreamSource(
        file), Unit.class);
Unit unit = root.getValue();

尝试一下:

Unit unit = JAXBIntrospector.getValue(jaxbUnmarshaller.unmarshal(new StreamSource(
        file), Unit.class);

答案 3 :(得分:0)

使用此方法可以使用javax.xml.bind.JAXBElement实例或@XmlRootElement注释Java类的实例:

public <T> T unmarshal(Source queryResults, String modelPackages) {
        T resultObject = null;
        try {
            JAXBContext jc = JAXBContext.newInstance(modelPackages);
            Unmarshaller u = jc.createUnmarshaller();
            resultObject = (T) JAXBIntrospector.getValue(u.unmarshal(queryResults));
        } catch (JAXBException e) {
            LOG.error(e.getMessage(), e);
        } catch (ClassCastException e) {
            LOG.error(e.getMessage(), e);
        }
        return resultObject;
    }

编辑:

你有理由,我推出了这个代码,因为这是我为一个项目所做的代码,而且我觉得它更可重复使用。

所以对你提问:

为什么JAXB将org.foo.bar.Unit包装到JAXBElement中以进行上述组合?

因为您告诉它使用<xsd:any processContents="skip" />执行此操作:)

好吧,当你把它放在你的XSD中时,就像你使用注释一样:

@XmlAnyElement(lax=false)

使用那个标签/注释你要对JAXB说:&#39;这里有一些东西,一堆节点,(@ XMLAnyElement)你不应该解析;让它作为DOM对象,请(lax = false)&#39;试试:

processContents=lax

这样它应该尝试将你的xml解析为域对象,如果它能找到它或者JAXBElement,它将返回Unit或者:

processContents=strict

将尝试将其解析为您的域对象

答案 4 :(得分:0)

经过一些谷歌搜索后,我找到了答案,这基本上由Kohsuke Kawaguchi在Why does JAXB put @XmlRootElement sometimes but not always中给出。事实证明,添加@XmlRootElement注释的决定是生成辅助工厂方法的对手。一个应该启用<xjc:simple />优化,JAXB将假设所有元素都是根元素:

<xsd:schema xmlns:bar="http://www.foo.org/bar"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
            xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
            targetNamespace="http://www.foo.org/bar"
            jaxb:extensionBindingPrefixes="xjc" jaxb:version="2.1" elementFormDefault="qualified">

    <xsd:annotation>
        <xsd:appinfo>
            <jaxb:globalBindings>
                <xjc:simple />
            </jaxb:globalBindings>
        </xsd:appinfo>
    </xsd:annotation>

    <xsd:element name="unit" type="bar:unit" />

    <xsd:complexType name="unit">
        <xsd:sequence>
            <xsd:any processContents="skip" />
        </xsd:sequence>
    </xsd:complexType>

</xsd:schema>