No @XmlRootElement generato da JAXB


209

Sto cercando di generare classi Java dalla versione 4.5 di FpML (Finanial Products Markup Language). Viene generata una tonnellata di codice, ma non posso usarlo. Cercando di serializzare un documento semplice ottengo questo:

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]

In effetti nessun corso ha l'annotazione @XmlRootElement, quindi cosa posso fare di sbagliato ?. Sto indicando xjc (JAXB 2.1) a fpml-main-4-5.xsd, che quindi include tutti i tipi.

Risposte:


261

Per collegare ciò che altri hanno già affermato o accennato, le regole con cui JAXB XJC decide se inserire o meno l' @XmlRootElementannotazione in una classe generata non sono banali ( vedi questo articolo ).

@XmlRootElementesiste perché il runtime JAXB richiede determinate informazioni per eseguire il marshalling / unmarshal di un determinato oggetto, in particolare il nome dell'elemento XML e lo spazio dei nomi. Non puoi semplicemente passare qualsiasi vecchio oggetto al Marshaller.@XmlRootElementfornisce queste informazioni.

L'annotazione è solo una comodità, tuttavia - JAXB non lo richiede. L'alternativa a è usare JAXBElementoggetti wrapper, che forniscono le stesse informazioni di@XmlRootElement , ma nella forma di un oggetto, piuttosto che un'annotazione.

Però, JAXBElement oggetti sono scomodi da costruire, poiché è necessario conoscere il nome dell'elemento XML e lo spazio dei nomi, cosa che la logica aziendale di solito non riesce.

Per fortuna, quando XJC genera un modello di classe, genera anche una classe chiamata ObjectFactory. Questo è in parte lì per la retrocompatibilità con JAXB v1, ma è anche un posto dove XJC può mettere metodi di fabbrica generati che creano JAXBElementinvolucri attorno ai tuoi oggetti. Gestisce il nome XML e lo spazio dei nomi per te, quindi non devi preoccuparti. Hai solo bisogno di esaminare i ObjectFactorymetodi (e per uno schema di grandi dimensioni, ce ne possono essere centinaia) per trovare quello che ti serve.


15
Soluzione caso speciale: quando è possibile modificare l'xsd utilizzato per la generazione di classi: dopo aver letto il collegamento fornito in questa risposta, la soluzione nel mio caso è stata quella di modificare il file xsd utilizzato per generare le classi: ho cambiato la definizione dell'elemento root in un definizione incorporata anziché utilizzare il riferimento a un tipo definito separatamente. Ciò consente a JAXB di impostare questo elemento come @XmlRootElement, il che non era possibile con elementType utilizzato in precedenza per l'elemento root.
Arthur

2
<scowl> modificando l'elemento root in modo che sia di tipo inline, tuttavia, tutte le classi diventano classi interne del tipo root. Inoltre, anche se il tipo di elemento radice è definito DOPO l'elemento radice stesso (apparentemente consentito dallo schema), JAXB non annoterà comunque con @XmlRootElement.
Pawel Veselov,

10
cioè new ObjectFactory().createPositionReport(positionReport)ritornaJAXBElement<PositionReport>
vikingsteve il

17
Cosa succede se il metodo ObjectFactory generato non crea un metodo che avvolge l'argomento in a JXBElement? Nel mio caso, il metodo factory è 0-arity e restituisce solo un newoggetto. (Perché ad alcune classi vengono dati gli helper del wrapper JAXBElement e altri no?) Immagino in tal caso che dobbiamo creare noi stessi il wrapper?
Carl G,

1
@CarlG Sono nella stessa situazione: nelle mie lezioni non compare XmlRootElement né JAXBElement. Hai trovato una soluzione per questo caso?
Mickael Marrache,

68

Questo è menzionato in fondo al post del blog già linkato sopra, ma per me funziona come un piacere:

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);

Preferisco la risposta contrassegnata, ma questo funziona anche per me.
Pedro Dusso,

1
cosa c'è jcnello snippet sopra?
Arun,

3
@ArunRaj è la classe JAXBContext
Gurnard,

51

Come accennato in una delle risposte di cui sopra, non si otterrà un XMLRootElement sull'elemento radice se nell'XSD il suo tipo è definito come un tipo denominato, poiché quel tipo denominato potrebbe essere utilizzato altrove nell'XSD. Prova a renderlo un tipo anonimo, ovvero anziché:

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

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

avresti:

<xsd:element name="myRootElement">
    <xsd:complexType>
    ...
    <xsd:complexType>
</xsd:element>

1
Questo non è vero per me. Il mio tipo è anonimo (incorporato nel mio elemento radice) e non viene generata alcuna annotazione XmlRootElement. Qualche idea?
Mickael Marrache,

38

@XmlRootElement non è necessario per unmarshalling - se si utilizza il modulo a 2 parametri di Unmarshaller # unmarshall.

Quindi, se invece di fare:

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

uno dovrebbe fare:

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

Quest'ultimo codice non richiederà l'annotazione @XmlRootElement a livello di classe UserType.


2
Conosci un modo altrettanto elegante di eseguire il marshalling di un oggetto che non ha XmlRootElement - senza avvolgerlo in un JAXBElement come menzionato da Skaffman, Gurnard et al?
Chris,

4
+1 Funziona perfettamente! Una modifica per maggiore chiarezza ... Nella tua soluzione "someSource" è un termine molto vago. Per elaborare: JAXBElement <TargetClazz> root = unmarshaller.unmarshal (nuovo StreamSource (nuovo File ("some.xml")), TargetClazz.class);
supernova,

4
Ulteriore elaborazione di "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

La risposta di Joe (Joe, 26 giugno 2009 alle 17:26) lo fa per me. La semplice risposta è che l'assenza di un'annotazione @XmlRootElement non è un problema se si esegue il marshalling di un JAXBElement. La cosa che mi ha confuso è che l'ObjectFactory generato ha 2 metodi createMyRootElement: il primo non accetta parametri e fornisce l'oggetto da scartare, il secondo prende l'oggetto da scartare e lo restituisce avvolto in un JAXBElement e il marshalling di JAXBElement funziona bene. Ecco il codice di base che ho usato (ne sono nuovo, quindi mi scuso se il codice non è stato formattato correttamente in questa risposta), in gran parte paralizzato dal testo del link :

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;
  }
}

2
Ho un caso in cui la mia classe ObjectFactory definisce solo metodi che restituiscono istanze regolari e non istanze JAXBElement ...
Mickael Marrache,

20

È possibile risolvere questo problema utilizzando l'associazione di Come generare le classi @XmlRootElement per i tipi di base in XSD? .

Ecco un esempio con 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>

Ecco il binding.xjbcontenuto del file

<?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>

3
In effetti, usando <xjc: simple> nel file binding.xjb ha funzionato. Ottima soluzione se non vuoi cambiare il tuo codice di marshalling o il tuo WSDL. Si noti che xjc: simple genera nomi di metodi diversi (plurale) per getter di raccolta (ad esempio getOrders anziché getOrder)
dvtoever

10

Come sapete, la risposta è utilizzare ObjectFactory (). Ecco un esempio del codice che ha funzionato per me :)

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();
    }

al tuo punto ... come posso usare i metodi JAXBElement <?> create ... () da ObjectFactory per gli elementi nidificati? cioè: <SOAP-ENV: Header> <wsse: Security> <wsse: UsernameToken> </ wsse: UsernameToken> </ wsse: Security> </ SOAP-ENV: Header> Ottengo: "impossibile eseguire il marshalling del tipo" UsernameTokenType " come elemento perché manca un'annotazione @XmlRootElement "
Angelina,

6

Non funziona neanche per noi. Ma abbiamo trovato un articolo ampiamente citato che aggiunge ALCUNI retroscena ... Lo collegherò qui per il bene della prossima persona: http://weblogs.java.net/blog/kohsuke/archive/2006/03 /why_does_jaxb_p.html


Questo ha funzionato bene per me, grazie. Ho anche scoperto che stavo marshalling l'oggetto JAXB sbagliato (non la radice come pensavo) nel processo di superamento. Ho dimenticato di creare un JAXBElement e stavo cercando di eseguire il marshalling solo dell'oggetto restituito dalla classe ObjectFactory che avevo ottenuto dal binding. Questo fondamentalmente ha risolto il problema (nel caso in cui qualcun altro si imbatte nello stesso problema).
Joe Bane,

1
404: "Siamo spiacenti che il sito java.net sia stato chiuso. La maggior parte dei progetti Open Source precedentemente ospitati su java.net sono stati trasferiti."
Tristan


6

Dopo due giorni di ricerca ho trovato la soluzione al problema. Puoi usare la classe ObjectFactory per risolvere il problema delle classi che non hanno @XmlRootElement . ObjectFactory ha sovraccaricato i metodi per avvolgerlo attorno a JAXBElement.

Metodo: 1 esegue la semplice creazione dell'oggetto.

Metodo: 2 avvolgerà l'oggetto con @JAXBElement .

Usa sempre il metodo: 2 per evitare javax.xml.bind.MarshalException - con l'eccezione collegata mancante un'annotazione @XmlRootElement.

Di seguito è riportato il codice di esempio

Metodo: 1 esegue la semplice creazione dell'oggetto

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

Metodo: 2 avvolgerà l'oggetto con @JAXBElement .

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

Esempio di codice funzionante:

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();

Grazie per aver fornito il riferimento al codice con il modello webservice di primavera perché stavo faticando a capirlo da un po 'di tempo!
RRR_J

5

Nel caso in cui la mia esperienza di questo problema dia a qualcuno un Eureka! momento .. aggiungerò quanto segue:

Inoltre stavo riscontrando questo problema, quando utilizzavo un file xsd che avevo generato usando l'opzione di menu "Genera xsd da istanza" di IntelliJ.

Quando ho accettato tutte le impostazioni predefinite di questo strumento, ha generato un file xsd che, quando utilizzato con jaxb, ha generato file java senza @XmlRootElement . In fase di esecuzione quando ho provato a eseguire il marshalling ho ottenuto la stessa eccezione di quella discussa in questa domanda.

Sono tornato allo strumento IntellJ e ho visto l'opzione predefinita nel menu a discesa "Tipo Desgin" (che ovviamente non capivo .. e ancora non capisco se sono onesto) era:

Tipo di Desgin:

"elementi locali / tipi complessi globali"

Ho cambiato questo in

"elementi / tipi locali"

, ora ha generato un xsd (sostanzialmente) diverso, che ha prodotto il @XmlRootElementse usato con jaxb. Non posso dire di aver capito bene dentro e fuori, ma ha funzionato per me.



4

I wrapper JAXBElement funzionano nei casi in cui @XmlRootElementJAXB non genera alcun valore . Questi wrapper sono disponibili nella ObjectFactoryclasse generata da maven-jaxb2-plugin. Ad esempio:

     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;
      }
 }

3

Hai provato a cambiare la tua xsd in questo modo?

<!-- create-logical-system -->
<xs:element name="methodCall">
  <xs:complexType>
    ...
  </xs:complexType>
</xs:element>

Questo ha funzionato per me con JDK 1.7u71. A un elemento di livello superiore viene assegnato @XmlRootElement da xjc. Inizialmente avevo solo un tipo complesso di alto livello. Dover avvolgere in un JAXBElement è semplicemente brutto.
Serge Merzliakov,

1

Per risolverlo, è necessario configurare un'associazione xml prima di compilare con wsimport, impostando generateElementProperty su false.

     <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>

il tag di avvolgimento dovrebbe essere<jaxb:bindings> ... <jaxws:bindings> ... </jaxws:bindings> ... </jaxb:bindings>
aliopi

0

L'argomento è piuttosto vecchio ma ancora rilevante nei contesti aziendali. Ho cercato di evitare di toccare gli xsds per aggiornarli facilmente in futuro. Ecco le mie soluzioni ..

1. Per lo più xjc:simpleè sufficiente

<?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>

Creerà principalmente XmlRootElements per l'importazione delle definizioni xsd.

2. Dividi le tue jaxb2-maven-pluginesecuzioni

Ho riscontrato che fa un'enorme differenza se provi a generare classi da più definizioni xsd invece di una definizione di esecuzione per xsd.

Quindi, se hai una definizione con multipli <source>, allora prova a dividerli:

          <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>

Il generatore non rileverà il fatto che una classe potrebbe essere sufficiente e quindi creare classi personalizzate per esecuzione. Ed è esattamente quello di cui ho bisogno;).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.