J'ai une configuration JAXB où j'utilise un @XmlJavaTypeAdapter pour remplacer les objets de type Person
par des objets de type PersonRef
qui ne contiennent que l'UUID de la personne. Cela fonctionne parfaitement bien. Cependant, le XML généré redéclare le même espace de nom (xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
) chaque fois qu'il est utilisé. Bien que ce soit généralement acceptable, cela ne me semble pas juste.
Comment configurer JAXB pour déclarer xmlns: xsi au tout début du document? Puis-je ajouter manuellement des déclarations d'espace de nom à l'élément racine?
Voici un exemple de ce que je veux réaliser:
Actuel:
<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>
Voulait:
<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>
Vous pouvez le faire avec le code:
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;
}
});
Pas ça joli, mais vous pouvez ajouter un schemaLocation vide à l’élément racine:
marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, "");
Il ressemble à un Problème de mappeur d’espace de noms de personnalisation JAXB
Lorsque vous marshallez un document XML à l'aide de JAXB 1.0, un objet Marshaller, un objet JAXB contrôlant le processus de marshalling, fournit des déclarations d'espace de nom dans le document XML obtenu. Parfois, le Marshaller génère de nombreuses déclarations d’espace de noms qui semblent redondantes, par exemple:
<?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 modifie ce comportement. Si vous utilisez JAXB 2.0 (ou une version ultérieure) pour marshaler un document XML, le Marshaller déclare tous les URI (Uniform Resource Identifiers) statiquement connus dans l'espace de noms, c'est-à-dire les URI utilisés comme noms d'éléments ou d'attributs dans les annotations JAXB.
JAXB peut également déclarer des espaces de nom supplémentaires au milieu d'un document XML, par exemple lorsqu'un nom qualifié (
QName
) utilisé en tant qu'attribut ou valeur d'élément nécessite un nouvel URI d'espace de nom, ou lorsqu'un nœud DOM (Document Object Model) L'arbre de contenu nécessite un nouvel URI d'espace de nom. Ce comportement peut générer un document XML contenant de nombreuses déclarations d'espace de nom avec des préfixes d'espace de nom générés automatiquement.Le problème est que les préfixes d’espace de noms générés automatiquement, tels que ns1, ns2 et ns3, ne sont pas conviviaux - ils n’aident généralement pas les gens à comprendre le code XML organisé.
Heureusement, JAXB version 2.0 (ou ultérieure) fournit une interface fournisseur (SPI) appelée
com.Sun.xml.bind.marshaller.NamespacePrefixMapper
que vous pouvez utiliser pour spécifier d'autres préfixes d'espace de nom utiles pour le marshalling.Lorsque le programme JAXBSample organise le document XML pour la première fois, il le fait sans utiliser de classe
NamespacePrefixMapper
. En conséquence, le Marshaller génère automatiquement un préfixe d'espace de nom, dans ce cas, ns2.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:JustAnElement xmlns:ns2="a">
<foo>true</foo>
</ns2:JustAnElement>
Exemple de configuration évitant la répétition de l'espace de noms:
Le deuxième tri effectué par le programme
JAXBSample
utilise une classeNamespacePrefixMapper
comme suit:
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;
}
}
La méthode
getPreferredPrefix()
de la classePreferredMapper
renvoie le préfixe préféré, dans ce cas,mappedNamespacea
à déclarer à l'élément racine du code XML organisé.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<mappedNamespacea:JustAnElement xmlns:mappedNamespacea="a">
<foo>true</foo>
</mappedNamespacea:JustAnElement>
si vous utilisez Maven, ajoutez simplement ceci à votre pom:
<dependency>
<groupId>com.Sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.2.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
pas besoin de PreferredMapper si vous configurez vos annotations comme défini dans l'exemple ci-dessus. Bien que je possède un fichier package-info.jave se configure comme suit:
@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;
C’est la meilleure réponse que je trouve sur le Web.
Les déclarations xsi:type
sont probablement créées car le type déclaré de JAXBElement
ne correspond pas au type de la valeur.
Si le ObjectFactory
a une méthode de création pour le JAXBElement
correct, vous devriez l'utiliser car il devrait renseigner correctement le QName
et le type info; sinon, j'essaierais de définir le type déclaré (second constructeur arg) de JAXBElement
sur String.class
(en supposant qu'il s'agisse du type de commentTest
) au lieu de CommentType.Comment
.
Propriétaire: Cbrettin
Vous pouvez laisser les espaces de noms être écrits une seule fois. Vous aurez besoin d'une classe proxy de XMLStreamWriter et d'un package-info.Java. Ensuite, vous ferez dans votre code:
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());
Classe de proxy (la méthode importante est "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;
Ajoutez votre correspondance nsPrefix
en procédant comme suit:
marshaller.setNamespaceMapping("myns","urn:foo");
C'est du XML, vous pouvez donc traiter la sortie en utilisant DOM ou XSLT pour vous débarrasser de plusieurs références d'espace de nom.