【问题标题】:Serialize Jaxb Pojos to multiple or different namespaces将 Jaxb Pojo 序列化到多个或不同的命名空间
【发布时间】:2016-05-02 06:54:59
【问题描述】:

考虑以下代码:

Main.java
====
package com.sample;

import com.sample.entity.Customer;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

public class Main {    

    public static void main(String[] args) throws JAXBException {
       JAXBContext jc = JAXBContext.newInstance(Customer.class);

       Customer customer = new Customer();
       customer.setId(123);

       Marshaller m = jc.createMarshaller();
       m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
       m.marshal(customer, System.out);    
    }
}


Customer.java
====
package com.sample.entity;

import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class Customer {
    private long id;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }
}

package-info.java
====
@XmlSchema(namespace = "http://www.example.org/package", elementFormDefault = XmlNsForm.QUALIFIED)
package com.sample.entity;

import javax.xml.bind.annotation.XmlSchema;
import javax.xml.bind.annotation.XmlNsForm;

我希望重用 Customer POJO 并创建两个不同的命名空间值。所以首先我想打印输出

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer xmlns="http://www.example.org/package">
    <id>123</id>
</customer>

像当前代码一样,然后使用来自同一个 pojo 的不同主命名空间创建第二个 xml,如下所示

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer xmlns="http://www.another.org/package">
    <id>123</id>
</customer>

或一起删除命名空间

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
    <id>123</id>
</customer>

【问题讨论】:

  • 您应该为 @XmlSchema 部分添加一个额外的代码 sn-p,因为它非常相关,但很容易被忽略。

标签: java xml xsd jaxb marshalling


【解决方案1】:

这是个好问题。

您可以在 Jaxb 节点级别上工作,而不是在类型级别上工作。 几乎不涉及任何开销。

关键是从您的业务逻辑类中创建/访问JAXBElement(节点级元素),并使用相应的QName 定义自定义它所代表的XML-Node。

QName 提供了几个自定义选项,从完全限定的命名空间到完全没有命名空间。唯一需要注意的是,使用没有前缀的命名空间是不可能的。如果您不提供,Jaxb 将使用默认命名空间。

示例

QName elaborateQName = new QName("http://www.another.org/package", "customer", "myNs");
QName simpleQName = new QName("customer");

JAXBElement jx1 = new JAXBElement(elaborateQName, customer.getClass(), customer);
JAXBElement jx2 = new JAXBElement(simpleQName, customer.getClass(), customer);

m.marshal(jx1, System.out);
m.marshal(jx2, System.out);

这将产生以下输出:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<myNs:customer xmlns:myNs="http://www.another.org/package">
    <id>123</id>
</myNs:customer>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
    <id>123</id>
</customer>

使用没有前缀的命名空间是不可能的。尝试使用空 prefix 或等效的 XMLConstants.DEFAULT_NS_PREFIX 作为参数的 QName 将导致生成默认前缀(如 ns1):

 // QName defaultNs = new QName("http://www.another.org/package", "customer", "");

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:customer xmlns:ns1="http://www.another.org/package">
    <id>123</id>
</ns2:customer>

使用 null 作为prefix的参数会产生异常:

java.lang.IllegalArgumentException: prefix cannot be "null" when creating a QName
at javax.xml.namespace.QName.<init>(QName.java:251)

【讨论】:

  • package-info.java 在示例中,由 jaxb 工具自动生成
  • 如果您对 JAXB 生成过程有影响,您可以尝试完全跳过 package-info.java 生成(通过 xjc 的 -npa 选项)
【解决方案2】:

如何用 MOXy 覆盖根 xmlns 属性:

EclipseLink MOXy 提供了对 JAXB 编组的轻松自定义,包括更改根元素的默认命名空间。用于覆盖默认命名空间的对象到 XML 映射 (OXM) 是:

<?xml version="1.0"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" version="2.5">
    <xml-schema element-form-default="QUALIFIED" namespace="http://www.another.org/package"/>
</xml-bindings>

如果没有默认命名空间,请改用&lt;xml-schema element-form-default="UNSET"/&gt;

MOXy 设置:

1) 将库添加到您的项目中:

<dependency>
    <groupId>org.eclipse.persistence</groupId>
    <artifactId>org.eclipse.persistence.moxy</artifactId>
    <version>2.6.2</version>
</dependency>

2) 将jaxb.properties 添加到您的对象模型包中以启用 MOXy 的工厂(例如 src/main/resources/com/sample/entity/jaxb.properties):

javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory

3) 根据您的 OXM 配置实例化您的上下文(在本例中,OXM 文件位于src/main/resources/com/sample/entity/my-oxm.xml):

Map<String, Source> metadata = Collections.singletonMap("com.sample.entity", new StreamSource(Customer.class.getResourceAsStream("my-oxm.xml")));
Map<String, Object> properties = Collections.singletonMap(JAXBContextProperties.OXM_METADATA_SOURCE, metadata);
JAXBContext jaxbContext = JAXBContext.newInstance(new Class[] {customer.getClass()}, properties);

然后您可以像往常一样在JAXBContext 上使用编组。为您要使用的每个不同的 OXM 文件实例化单独的上下文。

使用 MOXy 的其他自定义:

使用 MOXy 允许您在未来进一步自定义实体的编组,而无需更改对象模型,从而有效地使编组逻辑遵循 open/closed principle,即使您没有明确的 Marshaller 类。例如,如果您需要将 Customer 对象编组为 Person,您可以通过添加另一个 OXM 文件来实现:

<?xml version="1.0"?>
<xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" version="2.5">
    <xml-schema element-form-default="QUALIFIED" namespace="http://www.example.com/person"/>
    <java-types>
        <java-type name="com.sample.entity.Customer">
            <xml-root-element name="person"/>
            <java-attributes>
                <xml-attribute java-attribute="id" name="personId"/>
                <xml-element java-attribute="id" xml-path="someOtherId/text()"/>
            </java-attributes>
        </java-type>
    </java-types>
</xml-bindings>

【讨论】:

    猜你喜欢
    • 2021-10-21
    • 2014-05-18
    • 2018-01-17
    • 2011-02-08
    • 2021-07-04
    • 2020-05-12
    • 2020-06-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多