Tidak ada @XmlRootElement yang dihasilkan oleh JAXB

209

Saya mencoba menghasilkan kelas Java dari FpML (Finanial Products Markup Language) versi 4.5. Banyak kode dihasilkan, tetapi saya tidak bisa menggunakannya. Mencoba membuat serial dokumen yang sederhana saya dapatkan ini:

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]

Sebenarnya tidak ada kelas yang memiliki anotasi @XmlRootElement, jadi apa yang bisa saya lakukan salah ?. Saya menunjuk xjc (JAXB 2.1) ke fpml-main-4-5.xsd, yang kemudian mencakup semua jenis.

robinr
sumber

Jawaban:

261

Untuk menyatukan apa yang telah dinyatakan atau diisyaratkan oleh orang lain, aturan yang digunakan JAXB XJC untuk memutuskan apakah akan memberikan @XmlRootElementanotasi pada kelas yang dihasilkan atau tidak adalah tidak sepele ( lihat artikel ini ).

@XmlRootElementada karena runtime JAXB memerlukan informasi tertentu untuk menyusun / menghapus objek yang diberikan, khususnya nama elemen XML dan namespace. Anda tidak bisa begitu saja menyerahkan benda lama ke Marshaller. @XmlRootElementmemberikan informasi ini.

Anotasi hanyalah kenyamanan, namun - JAXB tidak memerlukannya. Alternatif untuk menggunakan JAXBElementobjek pembungkus, yang memberikan informasi yang sama seperti @XmlRootElement, tetapi dalam bentuk objek, bukan anotasi.

Namun, JAXBElement objek canggung untuk dibangun, karena Anda perlu mengetahui nama elemen XML dan namespace, yang logika bisnis biasanya tidak.

Untungnya, ketika XJC menghasilkan model kelas, itu juga menghasilkan kelas yang disebut ObjectFactory. Ini sebagian ada untuk kompatibilitas mundur dengan JAXB v1, tetapi juga ada sebagai tempat bagi XJC untuk menempatkan metode pabrik yang dihasilkan yang membuat JAXBElementpembungkus di sekitar objek Anda sendiri. Ini menangani nama XML dan namespace untuk Anda, jadi Anda tidak perlu khawatir tentang hal itu. Anda hanya perlu melihat melalui ObjectFactorymetode (dan untuk skema besar, bisa ada ratusan dari mereka) untuk menemukan yang Anda butuhkan.

skaffman
sumber
15
Solusi kasus khusus: ketika Anda dapat memodifikasi xsd yang digunakan untuk pembuatan kelas: Setelah membaca tautan yang disediakan dalam jawaban ini, solusi dalam kasus saya adalah memodifikasi file xsd yang digunakan untuk menghasilkan kelas: Saya mengubah definisi elemen root menjadi sebuah definisi inline alih-alih menggunakan referensi ke tipe yang didefinisikan secara terpisah. Ini memungkinkan JAXB untuk mengatur elemen ini sebagai @XmlRootElement, yang tidak mungkin dilakukan dengan elementType yang digunakan sebelumnya untuk elemen root.
Arthur
2
<scowl> mengubah elemen root menjadi tipe inline, namun, menjadikan semua kelas sebagai kelas dalam dari tipe root. Juga, bahkan jika tipe elemen root didefinisikan SETELAH elemen root itu sendiri (tampaknya diizinkan oleh skema), JAXB masih tidak akan membubuhi keterangan dengan @XmlRootElement.
Pawel Veselov
10
yaitu new ObjectFactory().createPositionReport(positionReport)pengembalianJAXBElement<PositionReport>
vikingsteve
17
Bagaimana jika metode ObjectFactory yang dihasilkan tidak membuat metode yang membungkus argumen dalam JXBElement? Dalam kasus saya, metode pabrik adalah 0-arity dan hanya mengembalikan newobjek. (Mengapa beberapa kelas diberikan pembungkus pembungkus JAXBElement dan yang lainnya tidak?) Saya kira dalam hal itu kita harus membuat pembungkus sendiri?
Carl G
1
@CarlG Saya dalam situasi yang sama - tidak ada XmlRootElement atau JAXBElement yang muncul di kelas saya. Sudahkah Anda menemukan solusi untuk kasus ini?
Mickael Marrache
68

Ini disebutkan di bagian bawah posting blog yang sudah ditautkan di atas tetapi ini berfungsi seperti hadiah untuk saya:

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
sumber
Saya lebih suka jawaban yang ditandai, tetapi ini juga cocok untuk saya.
Pedro Dusso
1
apa yang ada jcdi cuplikan di atas?
Arun
3
@ArunRaj itu adalah kelas JAXBContext
Gurnard
51

Seperti yang diisyaratkan pada salah satu jawaban di atas, Anda tidak akan mendapatkan XMLRootElement pada elemen root Anda jika di XSD tipenya didefinisikan sebagai tipe yang dinamai, karena tipe yang dinamai itu dapat digunakan di tempat lain di XSD Anda. Coba pilih jenis anonim, yaitu alih-alih:

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

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

Anda akan memiliki:

<xsd:element name="myRootElement">
    <xsd:complexType>
    ...
    <xsd:complexType>
</xsd:element>
Matthew Wise
sumber
1
Itu tidak benar bagi saya. Tipe saya anonim (tertanam di dalam elemen root saya) dan tidak ada penjelasan XmlRootElement yang dihasilkan. Ada ide?
Mickael Marrache
38

@XmlRootElement tidak diperlukan untuk unmarshalling - jika seseorang menggunakan bentuk 2 parameter Unmarshaller # unmarshall.

Jadi, jika bukannya melakukan:

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

yang harus dilakukan:

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

Kode yang terakhir tidak akan memerlukan penjelasan @XmlRootElement di tingkat kelas UserType.

Sayantam
sumber
2
Apakah Anda tahu cara yang sama anggun untuk menyusun objek yang tidak memiliki XmlRootElement - tanpa membungkusnya dalam JAXBElement seperti yang disebutkan oleh skaffman, Gurnard et al?
Chris
4
+1 Bekerja dengan sempurna! Satu edit untuk kejelasan lebih lanjut ... Dalam solusi Anda 'someSource' adalah istilah yang sangat kabur. Untuk menguraikan: JAXBElement <TargetClazz> root = unmarshaller.unmarshal (StreamSource baru (File baru ("some.xml")), TargetClazz.class);
supernova
4
Elaborasi lebih lanjut dari 'sumber daya':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

Jawaban Joe (Joe 26 Juni 2009 di 17:26) melakukannya untuk saya. Jawaban sederhananya adalah tidak adanya anotasi @XmlRootElement tidak menjadi masalah jika Anda membuat JAXBElement. Hal yang membingungkan saya adalah ObjectFactory yang dihasilkan memiliki 2 metode createMyRootElement - yang pertama tidak memerlukan parameter dan memberikan objek yang tidak terbungkus, yang kedua mengambil objek yang tidak terbungkus dan mengembalikannya dengan JAXBElement, dan menyatakan bahwa JAXBElement berfungsi dengan baik. Inilah kode dasar yang saya gunakan (saya baru dalam hal ini, jadi minta maaf jika kode tidak diformat dengan benar dalam balasan ini), sebagian besar diambil dari teks tautan :

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
sumber
2
Saya punya kasus di mana kelas ObjectFactory saya hanya mendefinisikan metode yang mengembalikan instance biasa dan bukan instance JAXBElement ...
Mickael Marrache
20

Anda dapat memperbaiki masalah ini menggunakan pengikatan dari Cara menghasilkan @XmlRootElement Kelas untuk Jenis Basis di XSD? .

Berikut ini adalah contoh dengan 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>

Ini adalah binding.xjbkonten 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>
Olivier. Roger
sumber
3
Memang, menggunakan <xjc: simple> dalam file binding.xjb berhasil. Solusi luar biasa jika Anda tidak ingin mengubah kode marshaling atau WSDL Anda. Perhatikan bahwa xjc: sederhana menghasilkan nama metode yang berbeda (jamak) untuk pengambil koleksi (getOrders, bukan getOrder misalnya)
dvtoever
10

Seperti yang Anda tahu jawabannya adalah menggunakan ObjectFactory (). Berikut adalah contoh kode yang bekerja untuk saya :)

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
sumber
to your point ... bagaimana cara menggunakan metode JAXBElement <?> create ... () dari ObjectFactory untuk elemen bersarang? yaitu: <SOAP-ENV: Header> <wsse: Security> <wsse: UsernameToken> </ wsse: UsernameToken> </ wsse: Keamanan> </ SOAP-ENV: Header> Saya mendapatkan: "tidak dapat marshal ketik" UsernameTokenType " sebagai elemen karena tidak ada anotasi @XmlRootElement "
Angelina
6

Itu tidak bekerja untuk kita juga. Tetapi kami memang menemukan artikel yang dikutip secara luas yang menambahkan BEBERAPA latar belakang ... Saya akan menautkannya di sini demi orang berikutnya: http://weblogs.java.net/blog/kohsuke/archive/2006/03 /why_does_jaxb_p.html

mcherm
sumber
Ini bekerja dengan baik untuk saya, terima kasih. Saya juga menemukan bahwa saya sedang mengatur objek JAXB yang salah (bukan akar seperti yang saya kira) dalam proses melewati ini. Saya lupa membuat JAXBElement dan mencoba untuk menyusun hanya objek yang dikembalikan dari kelas ObjectFactory yang saya peroleh dari pengikatan. Ini pada dasarnya menangani masalah ini sama sekali (seandainya ada orang lain yang menghadapi masalah yang sama).
Joe Bane
1
404: "Kami minta maaf situs java.net telah ditutup. Sebagian besar proyek Open Source yang sebelumnya di-host di java.net telah dipindahkan."
Tristan
6

Setelah sruggling selama dua hari saya menemukan solusi untuk masalah ini. Anda dapat menggunakan kelas ObjectFactory untuk mencari solusi untuk kelas-kelas yang tidak memiliki @XmlRootElement . ObjectFactory memiliki metode kelebihan beban untuk membungkusnya di sekitar JAXBElement.

Metode: 1 melakukan penciptaan objek secara sederhana.

Metode: 2 akan membungkus objek dengan @JAXBElement .

Selalu gunakan Metode: 2 untuk menghindari javax.xml.bind.MarshalException - dengan pengecualian terkait kehilangan anotasi @XmlRootElement.

Silakan temukan kode sampel di bawah ini

Metode: 1 melakukan penciptaan objek secara sederhana

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

Metode: 2 akan membungkus objek dengan @JAXBElement .

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

Contoh kode kerja:

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
sumber
Terima kasih telah memberikan referensi kode dengan templat layanan web pegas seperti yang berjuang untuk mengetahuinya selama beberapa waktu!
RRR_J
5

Seandainya pengalaman saya tentang masalah ini memberi seseorang Eureka! saat .. saya akan menambahkan berikut ini:

Saya juga mendapatkan masalah ini, ketika menggunakan file xsd yang saya hasilkan menggunakan opsi menu "Hasilkan xsd dari Instance dokumen" IntelliJ.

Ketika saya menerima semua default dari alat ini, itu menghasilkan file xsd yang ketika digunakan dengan jaxb, menghasilkan file java tanpa @XmlRootElement . Saat runtime ketika saya mencoba untuk marshal saya mendapat pengecualian yang sama seperti yang dibahas dalam pertanyaan ini.

Saya kembali ke alat IntellJ, dan melihat opsi default di drop-down "Desgin Type" (yang tentu saja saya tidak mengerti .. dan masih tidak jika saya jujur) adalah:

Jenis Desgin:

"elemen lokal / tipe kompleks Global"

Saya mengubah ini menjadi

"elemen / tipe lokal"

, sekarang menghasilkan xsd (berbeda) yang berbeda, yang menghasilkan @XmlRootElementsaat digunakan dengan jaxb. Tidak bisa mengatakan saya mengerti apa yang masuk dan keluar dari itu, tapi itu berhasil untuk saya.

johnm
sumber
4

Pembungkus JAXBElement berfungsi untuk kasus di mana tidak ada @XmlRootElementyang dihasilkan oleh JAXB. Pembungkus ini tersedia di ObjectFactorykelas yang dihasilkan oleh maven-jaxb2-plugin. Untuk misalnya:

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

Apakah Anda mencoba mengubah xsd Anda seperti ini?

<!-- create-logical-system -->
<xs:element name="methodCall">
  <xs:complexType>
    ...
  </xs:complexType>
</xs:element>
Tony
sumber
Ini bekerja untuk saya dengan JDK 1.7u71. Elemen tingkat atas akan diberi @XmlRootElement oleh xjc. Awalnya saya hanya memiliki tipe kompleks tingkat atas saja. Harus membungkus JAXBElement benar-benar jelek.
Serge Merzliakov
1

Untuk menggunakannya, Anda harus mengkonfigurasi xml binding sebelum dikompilasi dengan wsimport, mengatur generateElementProperty sebagai 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>
leandruol
sumber
tag pembungkus harus<jaxb:bindings> ... <jaxws:bindings> ... </jaxws:bindings> ... </jaxb:bindings>
aliopi
0

Topiknya cukup lama tetapi masih relevan dalam konteks bisnis perusahaan. Saya mencoba untuk menghindari menyentuh xsds agar mudah memperbaruinya di masa depan. Inilah solusi saya ..

1. Sebagian besar xjc:simplesudah cukup

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

Sebagian besar akan membuat XmlRootElements untuk mengimpor definisi xsd.

2. Bagi jaxb2-maven-plugineksekusi Anda

Saya temui bahwa itu membuat perbedaan besar jika Anda mencoba untuk menghasilkan kelas dari beberapa definisi xsd daripada definisi eksekusi per xsd.

Jadi, jika Anda memiliki definisi dengan banyak <source>, daripada hanya mencoba untuk membaginya:

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

Generator tidak akan menangkap fakta bahwa satu kelas mungkin cukup dan karenanya membuat kelas khusus per eksekusi. Dan itulah yang saya butuhkan;).

judomu
sumber