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>
Ответы
Ответ 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");