Kein von JAXB generiertes @XmlRootElement

209

Ich versuche, Java-Klassen aus der FpML-Version 4.5 (Finanial Products Markup Language) zu generieren. Eine Tonne Code wird generiert, aber ich kann ihn nicht verwenden. Beim Versuch, ein einfaches Dokument zu serialisieren, wird Folgendes angezeigt:

javax.xml.bind.MarshalException
  - with linked exception: [com.sun.istack.SAXException2: unable
  to marshal type
  "org.fpml._2008.fpml_4_5.PositionReport"
  as an element because it is missing an
  @XmlRootElement annotation]

Tatsächlich haben keine Klassen die Annotation @XmlRootElement. Was kann ich also falsch machen? Ich zeige xjc (JAXB 2.1) auf fpml-main-4-5.xsd, das dann alle Typen enthält.

Robinr
quelle

Antworten:

261

Um zusammenzufassen, was andere bereits gesagt oder angedeutet haben, sind die Regeln, nach denen JAXB XJC entscheidet, ob die @XmlRootElementAnnotation einer generierten Klasse hinzugefügt werden soll oder nicht, nicht trivial ( siehe diesen Artikel ).

@XmlRootElementexistiert, weil die JAXB-Laufzeit bestimmte Informationen benötigt, um ein bestimmtes Objekt zu marshallen / unmarshalen, insbesondere den Namen und den Namespace des XML-Elements. Sie können nicht einfach ein altes Objekt an den Marshaller übergeben. @XmlRootElementbietet diese Informationen.

Die Anmerkung ist jedoch nur eine Annehmlichkeit - JAXB benötigt sie nicht. Die Alternative dazu besteht darin, JAXBElementWrapper-Objekte zu verwenden, die dieselben Informationen wie @XmlRootElement, jedoch in Form eines Objekts, anstelle einer Anmerkung bereitstellen .

Das JAXBElementErstellen von Objekten ist jedoch umständlich, da Sie den Namen und den Namespace des XML-Elements kennen müssen, was in der Geschäftslogik normalerweise nicht der Fall ist.

Zum Glück generiert XJC beim Generieren eines Klassenmodells auch eine Klasse namens ObjectFactory. Dies dient zum Teil der Abwärtskompatibilität mit JAXB v1, ist aber auch ein Ort, an dem XJC generierte Factory-Methoden platzieren kann, mit denen JAXBElementWrapper um Ihre eigenen Objekte erstellt werden. Es behandelt den XML-Namen und den Namespace für Sie, sodass Sie sich darüber keine Gedanken machen müssen. Sie müssen nur die ObjectFactoryMethoden durchsehen (und für große Schemata können es Hunderte sein), um die gewünschte zu finden.

Skaffman
quelle
15
Sonderfalllösung: Wenn Sie die für die Klassengenerierung verwendete xsd ändern können: Nach dem Lesen des in dieser Antwort angegebenen Links bestand die Lösung in meinem Fall darin, die zur Generierung der Klassen verwendete xsd-Datei zu ändern: Ich habe die Definition des Stammelements in eine geändert Inline-Definition anstelle der Verwendung des Verweises auf einen separat definierten Typ. Dadurch kann JAXB dieses Element als @XmlRootElement festlegen, was mit dem zuvor für das Stammelement verwendeten elementType nicht möglich war.
Arthur
2
Wenn Sie das Stammelement so ändern, dass es vom Inline-Typ ist, werden alle Klassen zu inneren Klassen des Stammtyps. Auch wenn der Stammelementtyp NACH dem Stammelement selbst definiert ist (anscheinend vom Schema zugelassen), wird JAXB immer noch nicht mit @XmlRootElement kommentieren.
Pawel Veselov
10
dh new ObjectFactory().createPositionReport(positionReport)kehrt zurückJAXBElement<PositionReport>
vikingsteve
17
Was ist, wenn die generierte ObjectFactory-Methode keine Methode erstellt, die das Argument in a umschließt JXBElement? In meinem Fall ist die Factory-Methode 0-arity und gibt nur ein newObjekt zurück. (Warum erhalten einige Klassen JAXBElement-Wrapper-Helfer und andere nicht?) In diesem Fall müssen wir den Wrapper wohl selbst erstellen?
Carl G
1
@CarlG Ich bin in der gleichen Situation - in meinen Klassen werden weder XmlRootElement noch JAXBElement angezeigt. Haben Sie eine Lösung für diesen Fall gefunden?
Mickael Marrache
68

Dies wird am Ende des bereits oben verlinkten Blog-Beitrags erwähnt, aber das funktioniert für mich wie ein Vergnügen:

Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(new JAXBElement<MyClass>(new QName("uri","local"), MyClass.class, myClassInstance), System.out);
Gurnard
quelle
Ich bevorzuge die markierte Antwort, aber das funktioniert auch bei mir.
Pedro Dusso
1
Was ist jcin dem obigen Ausschnitt?
Arun
3
@ArunRaj es ist die JAXBContext-Klasse
Gurnard
51

Wie in einer der obigen Antworten angedeutet, erhalten Sie kein XMLRootElement für Ihr Stammelement, wenn der Typ in der XSD als benannter Typ definiert ist, da dieser benannte Typ an anderer Stelle in Ihrer XSD verwendet werden könnte. Versuchen Sie es mit einem anonymen Typ, dh anstelle von:

<xsd:element name="myRootElement" type="MyRootElementType" />

<xsd:complexType name="MyRootElementType">
...
</xsd:complexType>

du würdest haben:

<xsd:element name="myRootElement">
    <xsd:complexType>
    ...
    <xsd:complexType>
</xsd:element>
Matthew Wise
quelle
1
Das stimmt nicht für mich. Mein Typ ist anonym (eingebettet in mein Root-Element) und es wird keine XmlRootElement-Annotation generiert. Irgendeine Idee?
Mickael Marrache
38

@XmlRootElement wird für das Unmarshalling nicht benötigt - wenn man die 2-Parameter-Form von Unmarshaller # unmarshall verwendet.

Also, wenn anstatt zu tun:

UserType user = (UserType) unmarshaller.unmarshal(new StringReader(responseString));

man sollte tun:

JAXBElement<UserType> userElement = unmarshaller.unmarshal(someSource, UserType.class);
UserType user = userElement.getValue();

Für den letzteren Code ist keine Annotation @XmlRootElement auf UserType-Klassenebene erforderlich.

Sayantam
quelle
2
Kennen Sie eine ebenso elegante Möglichkeit, ein Objekt ohne XmlRootElement zu marshallen - ohne es in ein JAXBElement zu wickeln, wie von Skaffman, Gurnard et al. Erwähnt?
Chris
4
+1 Funktioniert perfekt! Eine Bearbeitung für mehr Klarheit ... In Ihrer Lösung ist 'someSource' ein sehr vager Begriff. Zur Erläuterung: JAXBElement <TargetClazz> root = unmarshaller.unmarshal (neue StreamSource (neue Datei ("some.xml")), TargetClazz.class);
Supernova
4
Weitere Ausarbeitung von 'someSource':String pathname = "file.xml"; InputStream stream = new FileInputStream(pathname); JAXBContext jaxbContext = JAXBContext.newInstance(UserType.class); Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller(); XMLInputFactory factory = XMLInputFactory.newInstance(); XMLEventReader someSource = factory.createXMLEventReader(stream); JAXBElement<UserType> userElement = jaxbUnmarshaller.unmarshal(someSource, UserType.class); UserType user = userElement.getValue();
Steve Pitchers
21

Joes Antwort (Joe 26. Juni 09 um 17:26) macht es für mich. Die einfache Antwort lautet, dass das Fehlen einer @ XMLRootElement-Annotation kein Problem darstellt, wenn Sie ein JAXBElement marshallen. Was mich verwirrt hat, ist, dass die generierte ObjectFactory über zwei createMyRootElement-Methoden verfügt - die erste verwendet keine Parameter und gibt das nicht umschlossene Objekt an, die zweite nimmt das nicht umhüllte Objekt und gibt es in ein JAXBElement verpackt zurück, und das Marshalling, dass JAXBElement einwandfrei funktioniert. Hier ist der grundlegende Code, den ich verwendet habe (ich bin neu in diesem Bereich, also entschuldige mich, wenn der Code in dieser Antwort nicht richtig formatiert ist), der größtenteils aus dem Linktext stammt :

ObjectFactory objFactory = new ObjectFactory();
MyRootElement root = objFactory.createMyRootElement();
...
// Set root properties
...
if (!writeDocument(objFactory.createMyRootElement(root), output)) {
    System.err.println("Failed to marshal XML document");
}
...

private boolean writeDocument(JAXBElement document, OutputStream output) {

  Class<?> clazz = document.getValue().getClass();
  try {
    JAXBContext context =
        JAXBContext.newInstance(clazz.getPackage().getName());
    Marshaller m = context.createMarshaller();
    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
    m.marshal(document, output);
    return true;

  } catch (JAXBException e) {
    e.printStackTrace(System.err);
    return false;
  }
}
Yaqoob
quelle
1
Ich habe einen Fall, in dem meine ObjectFactory-Klasse nur Methoden definiert, die reguläre Instanzen und keine JAXBElement-Instanzen zurückgeben ...
Mickael Marrache
20

Sie können dieses Problem mithilfe der Bindung unter Generieren von @ XMLRootElement-Klassen für Basistypen in XSD beheben . .

Hier ist ein Beispiel mit Maven

        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>jaxb2-maven-plugin</artifactId>
            <version>1.3.1</version>
            <executions>
                <execution>
                    <id>xjc</id>
                    <goals>
                        <goal>xjc</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <schemaDirectory>src/main/resources/xsd</schemaDirectory>
                <packageName>com.mycompany.schemas</packageName>
                <bindingFiles>bindings.xjb</bindingFiles>
                <extension>true</extension>
            </configuration>
        </plugin>

Hier ist der binding.xjbDateiinhalt

<?xml version="1.0"?>
<jxb:bindings version="1.0" xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
              xmlns:xjc= "http://java.sun.com/xml/ns/jaxb/xjc"
              jxb:extensionBindingPrefixes="xjc" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <jxb:bindings schemaLocation="path/to/myschema.xsd" node="/xs:schema">
        <jxb:globalBindings>
            <xjc:simple/>
        </jxb:globalBindings>
    </jxb:bindings>
</jxb:bindings>
Olivier.Roger
quelle
3
In der Tat hat die Verwendung von <xjc: simple> in der Datei binding.xjb den Trick getan. Tolle Lösung, wenn Sie Ihren Marshalling-Code oder Ihre WSDL nicht ändern möchten. Beachten Sie, dass xjc: simple unterschiedliche Methodennamen (Plural) für Auflistungs-Getter generiert (z. B. getOrders anstelle von getOrder).
dvtoever
10

Wie Sie wissen, besteht die Antwort darin, die ObjectFactory () zu verwenden. Hier ist ein Beispiel des Codes, der für mich funktioniert hat :)

ObjectFactory myRootFactory = new ObjectFactory();

MyRootType myRootType = myRootFactory.createMyRootType();

try {

        File file = new File("./file.xml");
        JAXBContext jaxbContext = JAXBContext.newInstance(MyRoot.class);
        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();

        //output pretty printed
        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        JABXElement<MyRootType> myRootElement = myRootFactory.createMyRoot(myRootType);

        jaxbMarshaller.marshal(myRootElement, file);
        jaxbMarshaller.marshal(myRootElement, System.out);

    } catch (JAXBException e) {
        e.printStackTrace();
    }
Shehaaz
quelle
zu Ihrem Punkt ... wie verwende ich JAXBElement <?> create ... () -Methoden aus ObjectFactory für verschachtelte Elemente? dh: <SOAP-ENV: Header> <wsse: Security> <wsse: UsernameToken> </ wsse: UsernameToken> </ wsse: Security> </ SOAP-ENV: Header> Ich erhalte: "Typ" UsernameTokenType kann nicht gemarshallt werden " als Element, weil eine @ XMLRootElement-Annotation fehlt "
Angelina
6

Es funktioniert auch nicht für uns. Aber wir haben einen vielzitierten Artikel gefunden, der EINIGEN Hintergrund hinzufügt ... Ich werde hier für die nächste Person darauf verlinken: http://weblogs.java.net/blog/kohsuke/archive/2006/03 /why_does_jaxb_p.html

mcherm
quelle
Das hat bei mir gut funktioniert, danke. Ich habe auch festgestellt, dass ich das falsche JAXB-Objekt (nicht die Wurzel, wie ich dachte) gemarshallt habe, während ich dies durchlief. Ich habe vergessen, ein JAXBElement zu erstellen, und habe versucht, nur das zurückgegebene Objekt aus der ObjectFactory-Klasse zu marshallen, die ich durch Binden erhalten habe. Dies hat sich im Grunde genommen vollständig um das Problem gekümmert (falls jemand anderes auf dasselbe Problem stößt).
Joe Bane
1
404: "Es tut uns leid, dass die java.net-Site geschlossen wurde. Die meisten Open Source-Projekte, die zuvor auf java.net gehostet wurden, wurden verschoben."
Tristan
6

Nachdem ich zwei Tage lang gekämpft hatte, fand ich die Lösung für das Problem. Sie können die ObjectFactory- Klasse verwenden, um die Klassen zu umgehen, die nicht über @XmlRootElement verfügen . ObjectFactory hat Methoden überladen, um es um das JAXBElement zu wickeln.

Methode 1 das Objekt einfach.

Methode: 2 umschließt das Objekt mit @JAXBElement .

Verwenden Sie immer Methode: 2 , um javax.xml.bind.MarshalException zu vermeiden - bei verknüpfter Ausnahme fehlt eine @ XMLRootElement-Annotation.

Den Beispielcode finden Sie unten

Methode: 1 erstellt das Objekt einfach

public GetCountry createGetCountry() {
        return new GetCountry();
    }

Methode: 2 umschließt das Objekt mit @JAXBElement .

 @XmlElementDecl(namespace = "my/name/space", name = "getCountry")
 public JAXBElement<GetCountry> createGetCountry(GetCountry value) {
        return new JAXBElement<GetCountry>(_GetCountry_QNAME, GetCountry.class, null, value);
    }

Arbeitscodebeispiel:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
WebServiceTemplate springWSTemplate = context.getBean(WebServiceTemplate.class);

GetCountry request = new GetCountry();
request.setGuid("test_guid");

JAXBElement<GetCountryResponse> jaxbResponse = (JAXBElement<GetCountryResponse>)springWSTemplate .marshalSendAndReceive(new ObjectFactory().createGetCountry(request));

GetCountryResponse response = jaxbResponse.getValue();
Prasadg
quelle
Vielen Dank, dass Sie die Code-Referenz mit der Spring-Webservice-Vorlage angegeben haben, da wir seit einiger Zeit Schwierigkeiten hatten, dies herauszufinden!
RRR_J
5

Falls meine Erfahrung mit diesem Problem jemandem eine Eureka gibt! Moment .. Ich werde folgendes hinzufügen:

Dieses Problem trat auch auf, wenn ich eine xsd-Datei verwendete, die ich mit der Menüoption "xsd aus Instanzdokument generieren" von IntelliJ generiert hatte.

Als ich alle Standardeinstellungen dieses Tools akzeptierte, wurde eine xsd-Datei generiert, die bei Verwendung mit jaxb Java-Dateien mit der Nr @XmlRootElement. 1 generierte . Zur Laufzeit, als ich versuchte zu marshallen, bekam ich die gleiche Ausnahme wie in dieser Frage beschrieben.

Ich kehrte zum IntellJ-Tool zurück und sah die Standardoption in der Dropdown-Liste "Desgin Type" (die ich natürlich nicht verstanden habe ... und immer noch nicht, wenn ich ehrlich bin):

Desgin-Typ:

"lokale Elemente / globale komplexe Typen"

Ich habe das geändert in

"lokale Elemente / Typen"

Jetzt wurde ein (wesentlich) anderes xsd generiert, das @XmlRootElementbei Verwendung mit jaxb das erzeugte. Ich kann nicht sagen, dass ich die Vor- und Nachteile verstehe, aber es hat bei mir funktioniert.

Johnm
quelle
4

JAXBElement-Wrapper funktionieren in Fällen, in denen @XmlRootElementvon JAXB no generiert wird. Diese Wrapper sind in ObjectFactoryKlassen verfügbar , die von generiert wurden maven-jaxb2-plugin. Zum Beispiel:

     public class HelloWorldEndpoint {
        @PayloadRoot(namespace = NAMESPACE_URI, localPart = "person")
        @ResponsePayload
        public JAXBElement<Greeting> sayHello(@RequestPayload JAXBElement<Person> request) {

        Person person = request.getValue();

        String greeting = "Hello " + person.getFirstName() + " " + person.getLastName() + "!";

        Greeting greet = new Greeting();
        greet.setGreeting(greeting);

        ObjectFactory factory = new ObjectFactory();
        JAXBElement<Greeting> response = factory.createGreeting(greet);
        return response;
      }
 }
zer0Id0l
quelle
3

Haben Sie versucht, Ihre xsd so zu ändern?

<!-- create-logical-system -->
<xs:element name="methodCall">
  <xs:complexType>
    ...
  </xs:complexType>
</xs:element>
Tony
quelle
Dies funktionierte bei mir mit JDK 1.7u71. Einem Element der obersten Ebene wird von xjc das @ XMLRootElement zugewiesen. Anfangs hatte ich nur einen komplexen Typ der obersten Ebene. Ein JAXBElement einwickeln zu müssen, ist einfach hässlich.
Serge Merzliakov
1

Um dies zu beheben, sollten Sie vor dem Kompilieren mit wsimport eine XML-Bindung konfigurieren und generateElementProperty auf false setzen.

     <jaxws:bindings wsdlLocation="LOCATION_OF_WSDL"
      xmlns:jaxws="http://java.sun.com/xml/ns/jaxws"
      xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" 
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
      xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
         <jaxws:enableWrapperStyle>false</jaxws:enableWrapperStyle>
    <jaxws:bindings  node="wsdl:definitions/wsdl:types/xs:schema[@targetNamespace='NAMESPACE_OF_WSDL']">
      <jxb:globalBindings xmlns:jxb="http://java.sun.com/xml/ns/jaxb" xmlns:xs="http://www.w3.org/2001/XMLSchema">
            <xjc:generateElementProperty>false</xjc:generateElementProperty> 
      </jxb:globalBindings>
  </jaxws:bindings>
</jaxws:bindings>
Leandruol
quelle
Das Wrapping-Tag sollte sein<jaxb:bindings> ... <jaxws:bindings> ... </jaxws:bindings> ... </jaxb:bindings>
Aliopi
0

Das Thema ist ziemlich alt, aber im geschäftlichen Kontext von Unternehmen immer noch relevant. Ich habe versucht zu vermeiden, die xsds zu berühren, um sie in Zukunft einfach zu aktualisieren. Hier sind meine Lösungen ..

1. Meistens xjc:simpleist ausreichend

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jxb:bindings version="2.0" xmlns:jxb="http://java.sun.com/xml/ns/jaxb"
    xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc"
    jxb:extensionBindingPrefixes="xjc">

    <jxb:globalBindings>
        <xjc:simple/> <!-- adds @XmlRootElement annotations -->
    </jxb:globalBindings>

</jxb:bindings>

Es werden hauptsächlich XmlRootElements zum Importieren von xsd-Definitionen erstellt.

2. Teilen Sie Ihre jaxb2-maven-pluginHinrichtungen

Ich habe festgestellt, dass es einen großen Unterschied macht, wenn Sie versuchen, Klassen aus mehreren xsd-Definitionen anstelle einer Ausführungsdefinition pro xsd zu generieren.

Wenn Sie also eine Definition mit mehreren haben <source>, versuchen Sie einfach, diese zu teilen:

          <execution>
            <id>xjc-schema-1</id>
            <goals>
              <goal>xjc</goal>
            </goals>
            <configuration>
              <xjbSources>
                <xjbSource>src/main/resources/xsd/binding.xjb</xjbSource>
              </xjbSources>
              <sources>
                <source>src/main/resources/xsd/definition1/</source>
              </sources>
              <clearOutputDir>false</clearOutputDir>
            </configuration>
          </execution>

          <execution>
            <id>xjc-schema-2</id>
            <goals>
              <goal>xjc</goal>
            </goals>
            <configuration>
              <xjbSources>
                <xjbSource>src/main/resources/xsd/binding.xjb</xjbSource>
              </xjbSources>
              <sources>
                <source>src/main/resources/xsd/definition2/</source>
              </sources>
              <clearOutputDir>false</clearOutputDir>
            </configuration>
          </execution>

Der Generator erkennt nicht, dass möglicherweise eine Klasse ausreicht, und erstellt daher pro Ausführung benutzerdefinierte Klassen. Und genau das brauche ich;).

Judomu
quelle