Ні @XmlRootElement, створений JAXB


209

Я намагаюся генерувати класи Java з версії 4.5 мови FpML (Finanial Products Markup Language). Створюється тонни коду, але я не можу його використовувати. Намагаючись серіалізувати простий документ, я отримую це:

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]

Насправді жоден клас не має анотації @XmlRootElement, тож що я можу робити неправильно ?. Я вказую xjc (JAXB 2.1) на fpml-main-4-5.xsd, який потім включає всі типи.

Відповіді:


261

Щоб пов’язати разом те, що інші вже заявили чи натякнули, правила, за якими JAXB XJC вирішує, ставити @XmlRootElementанотацію до сформованого класу, є нетривіальними ( див. Цю статтю ).

@XmlRootElementіснує тому, що час виконання JAXB вимагає певної інформації для маршалки / зняття маршалу певного об'єкта, зокрема імені XML та простору імен. Ви не можете просто передати будь-який старий об’єкт Маршаллеру. @XmlRootElementнадає цю інформацію.

Анотація - це лише зручність, однак JAXB цього не вимагає. Альтернативою є використання JAXBElementоб’єктів обгортки, які надають ту саму інформацію @XmlRootElement, що й у формі об'єкта, а не примітки.

Однак JAXBElementоб'єкти незручно будувати, оскільки вам потрібно знати ім'я та простір XML-елементів, яких бізнес-логіка зазвичай не має.

На щастя, коли XJC генерує модель класу, він також генерує клас під назвою ObjectFactory. Частково це існує для зворотної сумісності з JAXB v1, але це також є місцем для XJC, щоб розмістити генеровані фабричні методи, які створюють JAXBElementобгортки навколо власних об'єктів. Він обробляє для вас ім'я XML і простір імен, тому вам не потрібно турбуватися про це. Вам просто потрібно переглянути ObjectFactoryметоди (а для великих схем їх може бути сотні), щоб знайти потрібний.


15
Рішення для спеціального випадку: коли ви можете змінити xsd, що використовується для генерації класу: Після прочитання посилання, наданого в цій відповіді, рішенням у моєму випадку було змінити файл xsd, який використовується для створення класів: я змінив визначення елемента root на вбудоване визначення замість використання посилання на тип, визначений окремо. Це дозволяє JAXB встановити цей елемент як @XmlRootElement, що було неможливо з elementType, який раніше використовувався для кореневого елемента.
Артур

2
<scowl> зміна кореневого елемента має вбудований тип, однак робить усі класи внутрішніми класами кореневого типу. Крім того, навіть якщо тип кореневого елемента визначений ПІСЛЯ самого кореневого елемента (мабуть, це дозволено схемою), JAXB все одно не буде анотувати за допомогою @XmlRootElement.
Павло Веселов

10
тобто new ObjectFactory().createPositionReport(positionReport)повертаєтьсяJAXBElement<PositionReport>
vikingsteve

17
Що робити, якщо створений метод ObjectFactory не створює метод, який обертає аргумент у JXBElement? У моєму випадку заводський метод є 0-arity і просто повертає newоб'єкт. (Чому деякі класи дають помічники для обгортки JAXBElement, а інші - ні?) Я думаю, у такому випадку ми мусимо створити обгортку самостійно?
Карл Г

1
@CarlG Я в тій же ситуації - ні XmlRootElement, ні JAXBElement не з’являються в моїх класах. Ви знайшли рішення для цієї справи?
Мікаель Марраш

68

Це згадується в нижній частині публікації щоденника, вже пов’язаної вище, але це працює як подобається мені:

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

Я віддаю перевагу поміченій відповіді, але це працює і для мене.
Педро Дуссо

1
що jcу наведеному фрагменті?
Арун

3
@ArunRaj це клас
JAXBContext

51

Як натякнуто в одному з вищезазначених відповідей, ви не отримаєте XMLRootElement на вашому кореневому елементі, якщо в XSD його тип визначений як названий тип, оскільки цей названий тип може використовуватися в іншому місці вашого XSD. Спробуйте внести його в анонімний тип, тобто замість:

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

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

у вас буде:

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

1
Це для мене не вірно. Мій тип анонімний (вбудований всередину мого кореневого елемента), а анотація XmlRootElement не створюється. Будь-яка ідея?
Мікаель Марраш

38

@XmlRootElement не потрібен для видалення даних, якщо використовується форма 2 параметрів Unmarshaller # unmarshall.

Отже, якщо замість цього робити:

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

слід зробити:

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

Останній код не вимагатиме анотації @XmlRootElement на рівні класу UserType.


2
Чи знаєте ви настільки ж елегантний спосіб маршалки об’єкта, у якого немає XmlRootElement - без загортання його в JAXBElement, як згадували скаффман, Гурнард та ін?
Кріс

4
+1 Відмінно працює! Одна редакція для більшої ясності ... У вашому рішенні "someSource" - це дуже невиразний термін. Розробити: JAXBElement <TargetClazz> root = unmarshaller.unmarshal (новий StreamSource (новий файл ("some.xml")), TargetClazz.class);
супернова

4
Подальше опрацювання "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();
Стів

21

Відповідь Джо (Joe 26 червня '09 о 17:26) робить це для мене. Проста відповідь полягає в тому, що відсутність анотації @XmlRootElement не є проблемою, якщо ви маршалите JAXBElement. Що мене збентежило - це створений ObjectFactory має 2 методу createMyRootElement - перший не приймає жодних параметрів і дає розпакований об'єкт, другий бере нерозгорнутий об'єкт і повертає його, загорнутого в JAXBElement, і виконує кращий аналіз, що JAXBElement працює добре. Ось базовий код, який я використав (я новачок у цьому, тому вибачтесь, якщо код не відформатований правильно у цій відповіді), значною мірою посилається на текст посилання :

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

1
У мене є випадок, коли мій клас ObjectFactory визначає лише методи, що повертають регулярні екземпляри, а не екземпляри JAXBElement ...
Мікаел Марраш,

20

Виправити цю проблему можна за допомогою прив'язки з розділу Як створити класи @XmlRootElement для базових типів у XSD? .

Ось приклад з Мейвен

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

Ось binding.xjbвміст файлу

<?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
Дійсно, за допомогою <xjc: simple> у файліinding.xjb зробив свою справу. Дивовижне рішення, якщо ви не хочете змінювати код маршалінгу або свій WSDL. Зауважте, що xjc: simple генерує різні назви методів (множини) для отримання колекціонерів (наприклад, getOrders замість getOrder)
dvtoever

10

Як відомо, відповідь полягає у використанні ObjectFactory (). Ось зразок коду, який працював на мене :)

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

на ваш погляд ... як я можу використовувати JAXBElement <?> створити ... () методи з ObjectFactory для вкладених елементів? тобто: <SOAP-ENV: Header> <wsse: Безпека> <wsse: UsernameToken> </ wsse: UsernameToken> </ wsse: Безпека> </ SOAP-ENV: Header> Я отримую: "не в змозі ввести маршал типу" UsernameTokenType " як елемент, оскільки в ньому відсутня анотація @XmlRootElement "
Анджеліна

6

Не працює і для нас. Але ми знайшли широко цитовану статтю, яка містить деяку інформацію ... Я посилаюся на неї тут заради наступної людини: http://weblogs.java.net/blog/kohsuke/archive/2006/03 /why_does_jaxb_p.html


Це добре спрацювало для мене, дякую. Я також виявив, що я перебирав неправильний об'єкт JAXB (не корінь, як я думав) у процесі перегляду цього. Я забув створити JAXBElement і намагався маршалити тільки повернутий об'єкт із класу ObjectFactory, який я отримав при прив'язці. Це в основному вирішило це питання взагалі (якщо хтось інший зіткнеться з тією ж проблемою).
Джо Бейн

1
404: "На жаль, веб-сайт java.net закритий. Більшість проектів з відкритим кодом, які раніше розміщувалися на java.net, були переселені."
Трістан


6

Після проривання двох днів я знайшов рішення проблеми. Ви можете використовувати клас ObjectFactory для вирішення класів, у яких немає @XmlRootElement . ObjectFactory перевантажив методи, щоб обернути його навколо JAXBElement.

Метод: 1 робить просте створення об’єкта.

Спосіб: 2 обгорне об’єкт @JAXBElement .

Завжди використовуйте Метод 2, щоб уникнути javax.xml.bind.MarshalException - за пов’язаним винятком відсутня анотація @XmlRootElement.

Нижче наведено зразок коду

Метод: 1 робить просте створення об’єкта

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

Спосіб: 2 обгорне об’єкт @JAXBElement .

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

Зразок робочого коду:

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

Дякуємо, що надали посилання на код із шаблоном весняного веб-сервісу, бо намагалися розібратися в ньому досить довго!
RRR_J

5

У випадку, якщо мій досвід цієї проблеми когось дає Еврика! момент .. Я додам наступне:

Я також отримав цю проблему під час використання файлу xsd, який я створив, використовуючи параметр меню «Створити xsd з документа про екземпляр» IntelliJ.

Коли я прийняв усі параметри за замовчуванням цього інструменту, він створив файл xsd, який при використанні з jaxb генерував файли java з no @XmlRootElement. Під час виконання, коли я намагався маршалити, я отримав той самий виняток, який обговорювався в цьому питанні.

Я повернувся до інструменту IntellJ і побачив параметр за замовчуванням у спадному меню "Тип Desgin" (що, звичайно, я не зрозумів .. і досі не хочу, якщо я чесний):

Тип Desgin:

"локальні елементи / типи глобального комплексу"

Я змінив це на

"локальні елементи / типи"

, тепер він генерував (істотно) різний xsd, який виробляв @XmlRootElementпри використанні з jaxb. Не можу сказати, що я розумію все в цьому і поза, але воно працювало на мене.



4

Обгортки JAXBElement працюють у випадках, коли @XmlRootElementJAXB не генерується. Ці обгортки доступні в ObjectFactoryкласі, створеному компанією maven-jaxb2-plugin. Наприклад:

     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

Ви намагалися змінити xsd так?

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

Це працювало для мене з JDK 1.7u71. Елементу верхнього рівня присвоюється xjc @XmlRootElement. Спочатку у мене був лише складний тип верхнього рівня. Потрібно загорнутись у JAXBElement - це просто некрасиво.
Серж Мерзляков

1

Щоб розчинити його, вам слід налаштувати прив'язку xml до компіляції з wsimport, встановивши generatorElementProperty як хибну.

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

обгортковий тег має бути<jaxb:bindings> ... <jaxws:bindings> ... </jaxws:bindings> ... </jaxb:bindings>
aliopi

0

Тема досить стара, але все ще актуальна в бізнес-контекстах бізнесу. Я намагався уникати дотиків до xsds, щоб легко їх оновлювати в майбутньому. Ось мої рішення ..

1. Здебільшого xjc:simpleдостатньо

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

Він здебільшого створюватиме XmlRootElements для імпорту визначення xsd.

2. Розділіть ваші jaxb2-maven-pluginстрати

Я зіткнувся з тим, що це має величезну різницю, якщо ви намагаєтеся генерувати класи з декількох визначень xsd замість визначення виконання для xsd.

Тож якщо у вас є визначення з кількома <source>'s, то просто намагайтеся розділити їх:

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

Генератор не сприймає того, що одного класу може бути достатньо, і тому створювати спеціальні класи за виконанням. І саме це мені потрібно;).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.