webentwicklung-frage-antwort-db.com.de

Definieren Sie Spring-JAXB-Namespaces, ohne NamespacePrefixMapper zu verwenden

[Im Verlauf des Verstehens stark bearbeitet]

Ist es möglich, Spring Jaxb2Marshaller zur Verwendung eines benutzerdefinierten Satzes von Namespace-Präfixen zu bewegen (oder zumindest die in der Schemadatei/den Anmerkungen angegebenen Werte zu berücksichtigen), ohne eine Erweiterung eines NamespacePrefixMapper verwenden zu müssen?

Die Idee ist, eine Klasse mit einer "hat eine" Beziehung zu einer anderen Klasse zu haben, die wiederum eine Eigenschaft mit einem anderen Namespace enthält. Um dies besser zu veranschaulichen, betrachten Sie die folgende Projektskizze, die JDK1.6.0_12 verwendet (die letzte, die ich bei der Arbeit in die Finger bekommen kann). Ich habe Folgendes im Paket org.example.domain:

Main.Java:

package org.example.domain;

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(RootElement.class);

    RootElement re = new RootElement();
    re.childElementWithXlink = new ChildElementWithXlink();

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

}

RootElement.Java:

package org.example.domain;

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

@XmlRootElement(namespace = "www.example.org/abc", name="Root_Element")
public class RootElement {
  @XmlElement(namespace = "www.example.org/abc")
  public ChildElementWithXlink childElementWithXlink;

}

ChildElementWithXLink.Java:

package org.example.domain;

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

@XmlRootElement(namespace="www.example.org/abc", name="Child_Element_With_XLink")
public class ChildElementWithXlink {
  @XmlAttribute(namespace = "http://www.w3.org/1999/xlink")
  @XmlSchemaType(namespace = "http://www.w3.org/1999/xlink", name = "anyURI")
  private String href="http://www.example.org";

}

package-info.Java:

@javax.xml.bind.annotation.XmlSchema(
    namespace = "http://www.example.org/abc",
    xmlns = {
          @javax.xml.bind.annotation.XmlNs(prefix = "abc", namespaceURI ="http://www.example.org/abc"),
          @javax.xml.bind.annotation.XmlNs(prefix = "xlink", namespaceURI = "http://www.w3.org/1999/xlink")
            }, 
    elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
    package org.example.domain;

Beim Ausführen von Main.main () wird folgende Ausgabe ausgegeben:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:Root_Element xmlns:ns1="http://www.w3.org/1999/xlink" xmlns:ns2="www.example.org/abc">
<ns2:childElementWithXlink ns1:href="http://www.example.org"/>
</ns2:Root_Element>

was ich möchte, ist:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<abc:Root_Element xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:abc="www.example.org/abc">
<abc:childElementWithXlink xlink:href="http://www.example.org"/>
</abc:Root_Element>

Sobald dieser Teil funktioniert, wird das Problem mit der Konfiguration des Jaxb2Marshaller im Frühling (Frühling 2.5.6, wobei Spring-Oxm-Tiger-1.5.6 für Jaxb2Marshaller bereitgestellt wird) so gelöst, dass er dasselbe durch eine einfache Kontextkonfiguration und liefert ein Aufruf an Marschall (). 

Vielen Dank für Ihr anhaltendes Interesse an diesem Problem!

25
Gary Rowe

[Einige Änderungen, um eine JAXB-RI-Alternative anzubieten, befinden sich am Ende dieses Beitrags.]

Nun, nach viel Kopfkratzer musste ich endlich akzeptieren, dass ich für meine Umgebung (JDK1.6.0_12 unter Windows XP und JDK1.6.0_20 unter Mac Leopard) diese Aufgabe nicht lösen kann, ohne auf das Böse zurückzugreifen das ist der NamespacePrefixMapper. Warum ist es böse? Weil erzwingt, dass Sie sich auf eine interne JVM-Klasse in Ihrem Produktionscode verlassen. Diese Klassen sind nicht Bestandteil einer zuverlässigen Schnittstelle zwischen der JVM und Ihrem Code (d. H. Sie wechseln zwischen Aktualisierungen der JVM). 

Meiner Meinung nach sollte sich Sun mit diesem Problem befassen, oder jemand mit tieferen Kenntnissen könnte diese Antwort ergänzen - bitte! 

Weitermachen Da NamespacePrefixMapper nicht außerhalb der JVM verwendet werden soll, ist er nicht im standardmäßigen Kompilierungspfad von javac enthalten (ein Unterabschnitt von rt.jar, der von ct.sym gesteuert wird). Dies bedeutet, dass jeder Code, der davon abhängt, wahrscheinlich in einer IDE kompiliert wird, jedoch an der Befehlszeile (d. H. Maven oder Ant) fehlschlägt. Um dies zu umgehen, muss die Datei rt.jar explizit in den Build eingeschlossen werden. Windows scheint jedoch Probleme zu haben, wenn der Pfad Leerzeichen enthält.

Wenn Sie sich in dieser Position befinden, finden Sie hier ein Maven-Snippet, das Ihnen Ärger bereitet:

<dependency>
  <groupId>com.Sun.xml.bind</groupId>
  <artifactId>jaxb-impl</artifactId>
  <version>2.1.9</version>
  <scope>system</scope>
  <!-- Windows will not find rt.jar if it is in a path with spaces -->
  <systemPath>C:/temp/rt.jar</systemPath>
</dependency>

Beachten Sie den hartcodierten Müllweg zu einem seltsamen Ort für rt.jar. Sie könnten dies mit einer Kombination aus {Java.home} /lib/rt.jar umgehen, die auf den meisten Betriebssystemen funktioniert, aber aufgrund des Windows-Speicherplatzproblems nicht garantiert wird. Ja, Sie können Profile verwenden und entsprechend aktivieren ...

Alternativ können Sie in Ant Folgendes tun:

<path id="jre.classpath">
  <pathelement location="${Java.home}\lib" />
</path>
// Add paths for build.classpath and define {src},{target} as usual
<target name="compile" depends="copy-resources">
  <mkdir dir="${target}/classes"/>
  <javac bootclasspathref="jre.classpath" includejavaruntime="yes" debug="on" srcdir="${src}" destdir="${target}/classes" includes="**/*">
    <classpath refid="build.classpath"/>
  </javac>
</target>    

Und was ist mit der Jaxb2Marshaller Spring-Konfiguration? Nun, hier ist es, komplett mit meinem eigenen NamespacePrefixMapper:

Frühling:

<!-- JAXB2 marshalling (domain objects annotated with JAXB2 meta data) -->
<bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPaths">
  <list>
    <value>org.example.domain</value>
  </list>
</property>
<property name="marshallerProperties">
  <map>
    <!-- Good for JDK1.6.0_6+, lose 'internal' for earlier releases - see why it's evil? -->
    <entry key="com.Sun.xml.internal.bind.namespacePrefixMapper" value-ref="myCapabilitiesNamespacePrefixMapper"/>
    <entry key="jaxb.formatted.output"><value type="boolean">true</value></entry>
  </map>
</property>
</bean>

<!-- Namespace mapping prefix (ns1->abc, ns2->xlink etc) -->
<bean id="myNamespacePrefixMapper" class="org.example.MyNamespacePrefixMapper"/>

Dann mein NamespacePrefixMapper-Code:

public class MyNamespacePrefixMapper extends NamespacePrefixMapper {

  public String getPreferredPrefix(String namespaceUri,
                               String suggestion,
                               boolean requirePrefix) {
    if (requirePrefix) {
      if ("http://www.example.org/abc".equals(namespaceUri)) {
        return "abc";
      }
      if ("http://www.w3.org/1999/xlink".equals(namespaceUri)) {
        return "xlink";
      }
      return suggestion;
    } else {
      return "";
    }
  }
}

Nun da ist es. Ich hoffe, das hilft jemandem, die Schmerzen zu vermeiden, die ich durchgemacht habe. Oh, übrigens können Sie auf folgende Ausnahme stoßen, wenn Sie den oben genannten bösen Ansatz in Jetty verwenden:

Java.lang.IllegalAccessError: Die Klasse Sun.reflect.GeneratedConstructorAccessor23 kann nicht auf ihre Superklasse Sun.reflect.ConstructorAccessorImpl zugreifen

Also viel Glück beim Aussortieren. Hinweis: rt.jar im Bootclasspath Ihres Webservers.

[Zusätzliche Bearbeitungen zur Darstellung des JAXB-RI-Ansatzes (Reference Implementation)]

Wenn Sie die JAXB-RI-Bibliotheken in Ihren Code einfügen können, können Sie die folgenden Änderungen vornehmen, um den gleichen Effekt zu erzielen:

Main:

// Add a new property that implies external access
marshaller.setProperty("com.Sun.xml.bind.namespacePrefixMapper", new MyNamespacePrefixMapper());

MyNamespacePrefixMapper:

// Change the import to this
import com.Sun.xml.bind.marshaller.NamespacePrefixMapper;

Fügen Sie die folgende JAR-Datei aus dem JAXB-RI-Download (nach Durchlaufen der Lizenzrahmen) aus dem Ordner/lib hinzu:

jaxb-impl.jar

Wenn Sie Main.main () ausführen, wird die gewünschte Ausgabe erstellt.

12
Gary Rowe

(Stark bearbeitete Antwort)

Ich glaube, dass das Problem in Ihrem Code auf einige URI-Übereinstimmungen bei Namespaces zurückzuführen ist. Manchmal verwenden Sie " http://www.example.org/abc " und andere Male "www.example.org/abc". Folgendes sollte den Trick tun:

Main.Java

package org.example.domain;

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(RootElement.class); 
        System.out.println(jc);

        RootElement re = new RootElement(); 
        re.childElementWithXlink = new ChildElementWithXlink(); 

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

RootElement.Java

package org.example.domain; 

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

@XmlRootElement(namespace="http://www.example.org/abc", name="Root_Element") 
public class RootElement { 
  @XmlElement(namespace = "http://www.example.org/abc") 
  public ChildElementWithXlink childElementWithXlink; 

}

ChildElementWithXLink.Java

package org.example.domain;

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

@XmlRootElement(namespace="http://www.example.org/abc", name="Child_Element_With_XLink") 
public class ChildElementWithXlink { 
  @XmlAttribute(namespace = "http://www.w3.org/1999/xlink") 
  @XmlSchemaType(namespace = "http://www.w3.org/1999/xlink", name = "anyURI") 
  private String href="http://www.example.org"; 

} 

package-info.Java

@javax.xml.bind.annotation.XmlSchema( 
    namespace = "http://www.example.org/abc", 
    xmlns = { 
          @javax.xml.bind.annotation.XmlNs(prefix = "abc", namespaceURI ="http://www.example.org/abc"), 
          @javax.xml.bind.annotation.XmlNs(prefix = "xlink", namespaceURI = "http://www.w3.org/1999/xlink") 
            },  
    elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) 
    package org.example.domain; 

Wenn Sie nun Main.main () ausführen, erhalten Sie folgende Ausgabe:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<abc:Root_Element xmlns:abc="http://www.example.org/abc" xmlns:xlink="http://www.w3.org/1999/xlink">
    <abc:childElementWithXlink xlink:href="http://www.example.org"/>
</abc:Root_Element>
9
Blaise Doughan

@ Blaise: Können Sie die Dokumentation von MOXy mit diesen Informationen aktualisieren:

Spring JAXB-Namespaces definieren, ohne NamespacePrefixMapper zu verwenden

Ich denke, es wird dort nicht beschrieben, wie Sie die Namespace-Präfixe konfigurieren können.

1
basZero

Die JAXB-Implementierung in JDK 7 unterstützt das Namespacepräfix. Ich habe es mit JDK 1.6.0_21 ohne Erfolg versucht.

0
Sayantam