Подтвердить что ты не робот

JAXB: Как избежать повторного определения пространства имен для xmlns: xsi

У меня есть настройка JAXB, где я использую @XmlJavaTypeAdapter для замены объектов типа Person объектами типа PersonRef, которые содержат только идентификатор пользователя UUID. Это прекрасно работает. Однако сгенерированный 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>
4b9b3361

Ответ 1

Вы можете сделать это с помощью кода:

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;

                }
            });

Ответ 2

Не так красиво, но вы можете добавить пустую схему в корневой элемент:

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

Ответ 3

Похоже, что проблема с сопоставлением пространства имен JAXB.

Когда вы размещаете XML-документ с использованием JAXB 1.0, объект 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 объявляет все статически известные пространства имен Uniform Resource Identifiers (URI), то есть те URI, которые используются в качестве имен элементов или атрибутов в аннотациях JAXB.

JAXB может также объявлять дополнительные пространства имен в середине документа XML, например, когда квалифицированное имя (QName), которое используется как значение атрибута или элемента, требует нового URI пространства имен или когда объектная модель документа ( DOM) node в дереве контента требуется новый URI пространства имен. Такое поведение может привести к созданию XML-документа с большим количеством объявлений пространства имен с автоматически создаваемыми префиксами пространства имен.

Проблема в том, что автоматически создаваемые префиксы пространства имен, такие как ns1, ns2 и ns3, не являются удобными для пользователя - они, как правило, не помогают людям понять маршаллированный XML.

К счастью, JAXB 2.0 (или более поздняя версия) предоставляет интерфейс поставщика услуг (SPI) с именем com.sun.xml.bind.marshaller.NamespacePrefixMapper, который можно использовать для указания более полезного префиксы пространства имен для сортировки.

Когда программа 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>

Ответ 4

Если вы используете 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;

Ответ 5

Это лучший ответ, который я нашел в Интернете.

Объявления xsi:type, скорее всего, создаются, потому что объявленный тип JAXBElement не соответствует типу значения.

Если ObjectFactory имеет метод create для правильного JAXBElement, вы должны использовать его, поскольку он должен правильно заполнять как QName, так и информацию о типе; в противном случае я попытался бы установить объявленный тип (второй конструктор arg) от JAXBElement до String.class (предположим, что это тип commentTest) вместо CommentType.Comment.

Источник: http://www.java.net/forum/topic/glassfish/metro-and-jaxb/how-do-i-remove-namespace-declarations-child-elements

Владелец: cbrettin

Ответ 6

Вы можете позволить пространству имен писать только один раз. Вам понадобится прокси-класс 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());

Класс прокси (важным методом является "writeNamespace" ):

            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;

Ответ 7

Это XML, поэтому вы можете обрабатывать выходные данные с помощью DOM или XSLT, чтобы избавиться от нескольких ссылок на пространство имен.

Ответ 8

Добавьте свое сопоставление nsPrefix, выполнив следующее:

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