JAXB:如何避免xmlns:xsi的重复命名空间定义

时间:2010-02-12 13:25:51

标签: java xml jaxb

我有一个JAXB设置,我使用@XmlJavaTypeAdapter将Person类型的对象替换为仅包含该人的UUID的PersonRef类型的对象。这完全没问题。但是,生成的XML每次使用时都会重新声明相同的命名空间(xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance")。虽然这通常没问题,但感觉不对。

如何配置JAXB以在文档的最开头声明xmlns:xsi?我可以手动将名称空间声明添加到根元素吗?

以下是我想要实现的一个例子:

当前

<person uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a">
    <relation type="CHILD"> 
        <to xsi:type="personRef" uuid="56a930c0-5499-467f-8263-c2a9f9ecc5a0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> 
    </relation> 
    <relation type="CHILD"> 
        <to xsi:type="personRef" uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/> 
    </relation>
    <!-- SNIP: some more relations -->
</person>

通缉:

<person uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <relation type="CHILD"> 
        <to xsi:type="personRef" uuid="56a930c0-5499-467f-8263-c2a9f9ecc5a0"/> 
    </relation> 
    <relation type="CHILD"> 
        <to xsi:type="personRef" uuid="6ec0cf24-e880-431b-ada0-a5835e2a565a"/> 
    </relation>
    <!-- SNIP: some more relations -->
</person>

8 个答案:

答案 0 :(得分:17)

漂亮但你可以在根元素中添加一个空的schemaLocation:

marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "");

答案 1 :(得分:9)

看起来像JAXB customization Namespace mapper issue

  

使用JAXB 1.0编组XML文档时,Marshaller对象(控制编组过程的JAXB对象)在生成的XML文档中提供名称空间声明。有时Marshaller会生成许多看起来多余的命名空间声明,例如:

   <?xml version="1.0"?>
   <root>
      <ns1:element xmlns:ns1="urn:foo"> ... </ns1:element>
      <ns2:element xmlns:ns2="urn:foo"> ... </ns2:element>
      <ns3:element xmlns:ns3="urn:foo"> ... </ns3:element>
   </root>
  

JAXB 2.0更改了此行为。如果使用JAXB 2.0(或更高版本)编组XML文档,Marshaller将声明所有静态名称的命名空间统一资源标识符(URI),即在JAXB注释中用作元素或属性名称的URI。

     

JAXB还可以在XML文档的中间声明其他名称空间,例如,当用作属性或元素值的限定名称(QName)需要新的名称空间URI时,或者文档对象时内容树中的模型(DOM)节点需要新的名称空间URI。此行为可能会生成一个XML文档,该文档具有许多带有自动生成的命名空间前缀的命名空间声明。

     

问题是自动生成的名称空间前缀(如ns1,ns2和ns3)不是用户友好的 - 它们通常无法帮助人们理解编组的XML。

     

幸运的是,JAXB 2.0(或更高版本)提供了一个名为com.sun.xml.bind.marshaller.NamespacePrefixMapper的服务提供程序接口(SPI),您可以使用它来为编组指定更有用的名称空间前缀。

     

当JAXBSample程序第一次编组XML文档时,它会在不使用NamespacePrefixMapper类的情况下完成。因此,Marshaller会自动生成名称空间前缀,在本例中为ns2。

   <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
   <ns2:JustAnElement xmlns:ns2="a">
       <foo>true</foo>
   </ns2:JustAnElement>

避免命名空间重复的配置示例:

  

JAXBSample程序完成的第二次编组使用NamespacePrefixMapper类,如下所示:

   NamespacePrefixMapper m = new PreferredMapper();
               marshal(jc, e, m);

   public static class PreferredMapper extends NamespacePrefixMapper {
           @Override
           public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
               return "mappedNamespace" + namespaceUri;
           }
       }
  

getPreferredPrefix()类中的PreferredMapper方法返回首选前缀,在这种情况下,mappedNamespacea将在编组XML的根元素处声明。

   <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
   <mappedNamespacea:JustAnElement xmlns:mappedNamespacea="a">
       <foo>true</foo>
   </mappedNamespacea:JustAnElement>

答案 2 :(得分:6)

您可以使用以下代码执行此操作:

marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapper() {
                @Override
                public String[] getPreDeclaredNamespaceUris() {
                    return new String[] { 
                        XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI
                    };
                }

                @Override
                public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
                    if (namespaceUri.equals(XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI))
                        return "xsi";
                    if (namespaceUri.equals(XMLConstants.W3C_XML_SCHEMA_NS_URI))
                        return "xs";
                    if (namespaceUri.equals(WellKnownNamespace.XML_MIME_URI))
                        return "xmime";
                    return suggestion;

                }
            });

答案 3 :(得分:3)

如果您正在使用Maven,那么只需将其添加到您的pom:

<dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.2.2</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
如果您按照上面的示例中的定义配置注释,则

不需要PreferredMapper。虽然我有一个package-info.jave文件的配置如下:

@javax.xml.bind.annotation.XmlSchema(
        namespace = "mylovelynamespace1", 
        xmlns = {
                    @javax.xml.bind.annotation.XmlNs(prefix = "myns1", namespaceURI = "mylovelynamespace1"),
                    @javax.xml.bind.annotation.XmlNs(prefix = "myns2", namespaceURI = "mylovelynamespace2")
                }, 
                elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package com.mylovelycompanyname.package;

答案 4 :(得分:2)

这是我在网络上找到的最佳答案。

xsi:type声明最有可能被创建,因为JAXBElement的声明类型与值的类型不匹配。

如果ObjectFactory有正确JAXBElement的创建方法,则应使用该方法,因为它应正确填充QName和类型信息;否则我会尝试将JAXBElement的声明类型(第二个构造函数arg)设置为String.class(假设这是commentTest的类型而不是CommentType.Comment

来源: http://www.java.net/forum/topic/glassfish/metro-and-jaxb/how-do-i-remove-namespace-declarations-child-elements

拥有者: cbrettin

答案 5 :(得分:1)

您可以只将命名空间写入一次。您将需要XMLStreamWriter的代理类和package-info.java。然后你会在你的代码中做到:

StringWriter stringWriter = new StringWriter();
XMLStreamWriter writer = new Wrapper((XMLStreamWriter) XMLOutputFactory
                                                               .newInstance().createXMLStreamWriter(stringWriter));
JAXBContext jaxbContext = JAXBContext.newInstance(Collection.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
jaxbMarshaller.marshal(books, writer);
System.out.println(stringWriter.toString());

代理类(重要的方法是&#34; writeNamespace&#34;):

            class WrapperXMLStreamWriter implements XMLStreamWriter {

                   private final XMLStreamWriter writer;

                   public WrapperXMLStreamWriter(XMLStreamWriter writer) {
                       this.writer = writer;
                   }

                     //keeps track of what namespaces were used so that not to 
                     //write them more than once
                   private List<String> namespaces = new ArrayList<String>();

                   public void init(){
                       namespaces.clear();
                   }

                   public void writeStartElement(String localName) throws XMLStreamException {
                       init();
                       writer.writeStartElement(localName);

                   }

                   public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
                       init();
                       writer.writeStartElement(namespaceURI, localName);
                   }

                   public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
                       init();
                       writer.writeStartElement(prefix, localName, namespaceURI);
                   }

                   public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
                       if(namespaces.contains(namespaceURI)){ 
                           return;
                       }
                       namespaces.add(namespaceURI);
                       writer.writeNamespace(prefix, namespaceURI);
                   }

    // .. other delegation method, always the same pattern: writer.method() ...

}

package-info.java:

@XmlSchema(elementFormDefault=XmlNsForm.QUALIFIED, attributeFormDefault=XmlNsForm.UNQUALIFIED ,
        xmlns = { 
        @XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi")})
package your.package;

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

答案 6 :(得分:0)

它是XML,因此您可以使用DOM或XSLT处理输出以消除多个命名空间引用。

答案 7 :(得分:0)

通过执行以下操作添加nsPrefix映射:

marshaller.setNamespaceMapping("myns","urn:foo");