Нет @XmlRootElement, созданного JAXB

209

Я пытаюсь сгенерировать Java-классы из FpML (Finanial Products Markup Language) версии 4.5. Тонна кода генерируется, но я не могу его использовать. Пытаясь сериализовать простой документ, я получаю это:

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, который включает все типы.

robinr
источник

Ответы:

261

Чтобы связать воедино то, что другие уже заявили или намекнули, правила, по которым JAXB XJC решает, помещать @XmlRootElementаннотацию в сгенерированный класс, нетривиальны ( см. Эту статью ).

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

Однако аннотация - это просто удобство - JAXB этого не требует. Альтернативой является использование JAXBElementобъектов-оболочек, которые предоставляют ту же информацию @XmlRootElement, но в виде объекта, а не аннотации.

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

К счастью, когда XJC генерирует модель класса, он также генерирует класс с именем ObjectFactory. Это частично для обратной совместимости с JAXB v1, но XJC также может предоставить сгенерированные фабричные методы, которые создают JAXBElementобертки вокруг ваших собственных объектов. Он обрабатывает имя XML и пространство имен для вас, поэтому вам не нужно беспокоиться об этом. Вам просто нужно просмотреть ObjectFactoryметоды (а для больших схем их может быть сотни), чтобы найти тот, который вам нужен.

skaffman
источник
15
Решение для особого случая: когда вы можете изменить xsd, используемый для генерации классов: после прочтения ссылки, приведенной в этом ответе, в моем случае решение состояло в том, чтобы изменить файл xsd, используемый для генерации классов: я изменил определение корневого элемента на встроенное определение вместо использования ссылки на тип, определенный отдельно. Это позволяет JAXB установить этот элемент как @XmlRootElement, что было невозможно с elementType, который использовался ранее для корневого элемента.
Артур
2
<scowl> изменяет корневой элемент на встроенный тип, однако все классы становятся внутренними классами корневого типа. Кроме того, даже если тип корневого элемента определен ПОСЛЕ самого корневого элемента (очевидно, разрешенного схемой), JAXB по-прежнему не будет аннотировать @XmlRootElement.
Павел Веселов
10
то есть new ObjectFactory().createPositionReport(positionReport)возвращаетсяJAXBElement<PositionReport>
Викингстев
17
Что если сгенерированный метод ObjectFactory не создает метод, который оборачивает аргумент в JXBElement? В моем случае фабричный метод имеет 0-арность и просто возвращает 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
Gurnard
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.

Sayantam
источник
2
Знаете ли вы о столь же элегантном способе маршалинга объекта, у которого нет XmlRootElement - без обертывания его в JAXBElement, как упомянуто skaffman, Gurnard et al?
Крис
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 Jun 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;
  }
}
Якуб
источник
2
У меня есть случай, когда мой класс ObjectFactory определяет только методы, которые возвращают обычные экземпляры, а не экземпляры JAXBElement ...
Микаэль Маррач
20

Вы можете исправить эту проблему, используя привязку из Как создать классы @XmlRootElement для базовых типов в XSD? ,

Вот пример с 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>

Вот 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>
Olivier.Roger
источник
3
Действительно, использование <xjc: simple> в файле binding.xjb помогло. Отличное решение, если вы не хотите менять код маршалинга или WSDL. Обратите внимание , что XJC: простой генерирует различные имена методов (множественное число) для сбора геттеров (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();
    }
Shehaaz
источник
на ваш взгляд ... как использовать методы JAXBElement <?> create ... () из ObjectFactory для вложенных элементов? то есть: <SOAP-ENV: заголовок> <wsse: Security> <wsse: UsernameToken> </ wsse: UsernameToken> </ wsse: Security> </ SOAP-ENV: Header> Я получаю: «невозможно выполнить маршалинг типа« UsernameTokenType » как элемент, потому что в нем отсутствует аннотация @XmlRootElement "
Анджелина
6

У нас это тоже не работает. Но мы нашли широко цитируемую статью, в которой добавлено НЕКОТОРОЕ предысторию ... Я приведу ссылку на нее здесь ради следующего человека: http://weblogs.java.net/blog/kohsuke/archive/2006/03 /why_does_jaxb_p.html

mcherm
источник
Это хорошо сработало для меня, спасибо. Я также обнаружил, что в процессе прохождения этого процесса я использовал неправильный объект JAXB (а не root, как я думал). Я забыл создать JAXBElement и пытался упорядочить только возвращенный объект из класса ObjectFactory, который я получил от связывания. Это в основном позаботилось о проблеме в целом (на случай, если кто-то столкнется с той же проблемой).
Джо Бэйн
1
404: «К сожалению, сайт java.net закрыт. Большинство проектов с открытым исходным кодом, ранее размещенных на java.net, были перемещены».
Тристан,
6

После двух дней работы я нашел решение этой проблемы. Вы можете использовать класс ObjectFactory для обхода тех классов, которые не имеют @XmlRootElement . ObjectFactory имеет перегруженные методы, чтобы обернуть его вокруг JAXBElement.

Метод: 1 делает простое создание объекта.

Метод: 2 обернет объект с помощью @JAXBElement .

Всегда используйте Method: 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();
prasadg
источник
Спасибо за то, что дали ссылку на код с весенним шаблоном веб-сервиса, который пытался выяснить это в течение достаточно долгого времени!
RRR_J
5

В случае, если мой опыт этой проблемы дает кому-то Эврика! момент .. Я добавлю следующее:

Я также столкнулся с этой проблемой, когда использовал xsd-файл, сгенерированный с помощью опции меню IntelliJ «Создать xsd из документа экземпляра».

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

Я вернулся к инструменту IntellJ и увидел опцию по умолчанию в выпадающем списке «Desgin Type» (которая, конечно, я не понимал .. и все еще не понимаю, если честно) была:

Тип дизайна:

"Локальные элементы / Глобальные сложные типы"

Я изменил это на

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

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

johnm
источник
4

Оболочки JAXBElement работают в случаях, когда @XmlRootElementJAXB не генерирует no . Эти оболочки доступны в 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;
      }
 }
zer0Id0l
источник
3

Вы пытались изменить свой XSD, как это?

<!-- create-logical-system -->
<xs:element name="methodCall">
  <xs:complexType>
    ...
  </xs:complexType>
</xs:element>
Тони
источник
Это работало для меня с JDK 1.7u71. Элементу верхнего уровня назначается элемент @XmlRootElement с помощью xjc. Изначально у меня был только сложный тип верхнего уровня. Оборачивать в элемент JAXBElement просто некрасиво.
Серж Мерзляков
1

Чтобы решить эту проблему, вы должны настроить привязку xml перед тем, как скомпилировать с помощью wsimport, установив generateElementProperty как 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
источник
тег для упаковки должен быть<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>, просто попробуйте разделить их:

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

Генератор не уловит тот факт, что одного класса может быть достаточно, и, следовательно, создает пользовательские классы для выполнения. И это именно то, что мне нужно;).

judomu
источник