Нет @XmlRootElement, созданного JAXB
Я пытаюсь сгенерировать 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, который включает все типы.
17 ответов
Чтобы связать воедино то, что другие уже заявили или намекнули, правила, по которым JAXB XJC решает, ставить или нет @XmlRootElement
аннотации к сгенерированному классу нетривиальны ( см. эту статью).
@XmlRootElement
существует, потому что среда выполнения JAXB требует определенной информации, чтобы маршалировать / демаршировать данный объект, в частности, имя элемента XML и пространство имен. Вы не можете просто передать какой-нибудь старый объект Маршаллеру. @XmlRootElement
предоставляет эту информацию.
Однако аннотация - это просто удобство - JAXB этого не требует. Альтернативой является использование JAXBElement
объекты-обертки, которые предоставляют ту же информацию, что и @XmlRootElement
, но в виде объекта, а не аннотации.
Тем не мение, JAXBElement
Объекты создавать неудобно, так как вам нужно знать имя элемента XML и пространство имен, чего обычно нет в бизнес-логике.
К счастью, когда XJC генерирует модель класса, он также генерирует класс с именем ObjectFactory
, Это частично для обратной совместимости с JAXB v1, но XJC также может использовать сгенерированные фабричные методы, которые создают JAXBElement
обертки вокруг ваших собственных объектов. Он обрабатывает имя XML и пространство имен для вас, поэтому вам не нужно беспокоиться об этом. Вам просто нужно посмотреть через ObjectFactory
методы (а для больших схем их может быть сотни), чтобы найти тот, который вам нужен.
Это упомянуто в нижней части поста в блоге, уже связанного выше, но это работает как удовольствие для меня:
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);
Как указывалось в одном из приведенных выше ответов, вы не получите 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>
@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.
Вы можете решить эту проблему, используя привязку из Как создать классы @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>
Ответ Джо (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;
}
}
Как вы знаете, ответ заключается в использовании 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();
}
После двух дней работы я нашел решение этой проблемы. Вы можете использовать класс ObjectFactory для обхода тех классов, которые не имеют @XmlRootElement. ObjectFactory имеет перегруженные методы, чтобы обернуть его вокруг JAXBElement. Метод:1 выполняет простое создание объекта, а метод:2 обернет объект @JAXBElement. Всегда использовать метод:2, чтобы избежать javax.xml.bind.MarshalException - со связанным исключением отсутствует аннотация @XmlRootElement
Метод:1
public GetCountry createGetCountry() {
return new GetCountry();
}
Метод:2
@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("1f3e1771-3049-49f5-95e6-dc3732c3227b");
JAXBElement<GetCountryResponse> jaxbResponse = (JAXBElement<GetCountryResponse>)springWSTemplate .marshalSendAndReceive(new ObjectFactory().createGetCountry(request));
GetCountryResponse response = jaxbResponse.getValue();
В случае, если мой опыт этой проблемы дает кому-то Эврика! момент.. я добавлю следующее:
Я также столкнулся с этой проблемой, когда использовал xsd-файл, сгенерированный с помощью опции меню IntelliJ "Создать xsd из документа экземпляра".
Когда я принял все значения по умолчанию этого инструмента, он сгенерировал xsd-файл, который при использовании с jaxb генерировал java-файлы без @XmlRootElement
, Во время выполнения, когда я пытался выполнить маршализацию, я получил то же исключение, что обсуждалось в этом вопросе.
Я вернулся к инструменту IntellJ и увидел опцию по умолчанию в выпадающем меню "Desgin Type" (которая, конечно, я не понимал… и все еще не понимаю, если честно) была:
Тип Desgin:
"Локальные элементы / Глобальные сложные типы"
Я изменил это на
"локальные элементы / типы"
теперь он генерирует (существенно) другой xsd, который произвел @XmlRootElement
при использовании с jaxb. Не могу сказать, что я понимаю все это, но это сработало для меня.
Обертки JAXBElement работают для случаев, когда нет @XmlRootElement
генерируется JAXB. Эти обертки доступны в 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;
}
}
Тема довольно старая, но все еще актуальна в контексте корпоративного бизнеса. Я старался не трогать 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>
Генератор не улавливает тот факт, что одного класса может быть достаточно, и поэтому создает собственные классы для каждого выполнения. И это именно то, что мне нужно;).
У нас это тоже не работает. Но мы нашли широко цитируемую статью, в которой добавлено НЕКОТОРОЕ предысторию... Я приведу ссылку на нее здесь ради следующего человека: http://weblogs.java.net/blog/kohsuke/archive/2006/03/why_does_jaxb_p.html
Со сборкой Maven вы можете добавить @XmlRootElement
аннотирование
с "jaxb2-basics-annotate
"плагин.
Смотрите больше информации: см.
Сконфигурируйте Maven для генерации классов из XML-схемы с использованием JAXB
Итак, я использовал mavens
maven-jaxb2-plugin
чтобы сгенерировать классы из большого и сложного файла WSDL и столкнуться с этой проблемой. Проблема в том, что элементы WSDL, на которые ссылаются
complexType
определения как
type
и, следовательно, классы элементов не были созданы, и при попытке использовать классы complexType возникли недостающие
@XmlRootElement
ошибка.
На мой взгляд, изменение WSDL не является жизнеспособным решением, единственное практическое решение - разработать способ добавления недостающей аннотации во время генерации. Это также вызвало проблемы с сериализацией при маршалинге, потому что запрос отправлял неправильное имя элемента, а также не было класса с совпадающим именем элемента для ответа.
В итоге я использовал второй плагин maven
jaxb2-basics-annotate
который позволяет добавлять недостающие аннотации к требуемым классам с помощью файла привязки jaxb. Это избавляет вас от необходимости разбираться в этой проблеме без добавления ненужного кода, а также означает, что вы можете легко повторно сгенерировать, если вам понадобится использовать обновленный файл WSDL в будущем.
pom.xml (обратите внимание, что в конфигурации есть раздел плагинов для выполнения - расположение файла WSDL - /src/main/resources/wsdl/EstimatingService.wsdl)
<project>
...
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<jaxb2.version>0.14.0</jaxb2.version>
<jaxb2.annotate.version>1.1.0</jaxb2.annotate.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>${jaxb2.version}</version>
</dependency>
<dependency>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics-annotate</artifactId>
<version>${jaxb2.annotate.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jvnet.jaxb2.maven2</groupId>
<artifactId>maven-jaxb2-plugin</artifactId>
<version>${jaxb2.version}</version>
<executions>
<execution>
<id>estimating</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<schemaLanguage>WSDL</schemaLanguage>
<generateDirectory>target/generated-sources/acme/src/gen/estimating-service</generateDirectory>
<generatePackage>com.acme.shipping.estimating.service</generatePackage>
<schemaDirectory>${project.basedir}/src/main/resources/wsdl</schemaDirectory>
<schemaIncludes>
<include>EstimatingService.wsdl</include>
</schemaIncludes>
<bindingDirectory>${project.basedir}/src/main/resources/bindings</bindingDirectory>
<bindingIncludes>estimateServiceBinding.xjb</bindingIncludes>
<extension>true</extension>
<args>
<arg>-Xannotate</arg>
<arg>-XremoveAnnotation</arg>
</args>
<plugins>
<plugin>
<groupId>org.jvnet.jaxb2_commons</groupId>
<artifactId>jaxb2-basics-annotate</artifactId>
</plugin>
</plugins>
</configuration>
</execution>
...
// More executions here if you have multiple WSDL files (Dont forget to give it a different package name and id)
</executions>
</plugin>
</plugins>
</build>
...
</project>
estimateServiceBinding.xjb (JAXB связывание файла , используемый в данном примере - /src/main/resources/bindings/estimateServiceBinding.xjb)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<jaxb:bindings version="2.0" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" xmlns:xjc="http://java.sun.com/xml/ns/jaxb/xjc" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:annox="http://annox.dev.java.net" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
<jaxb:globalBindings generateElementProperty="false">
<xjc:simple />
</jaxb:globalBindings>
<!-- Target the schema section in the WSDL file using the given target namespace which contains the complexType definitions we want to annotate -->
<jaxb:bindings schemaLocation="../wsdl/EstimatingService.wsdl" node="//xs:schema[@targetNamespace='http://acme.com/schema/datatypes/v2']">
<jaxb:bindings node="xs:complexType[@name='GetQuickEstimateRequestContainer']">
<!-- Add the @XmlRootElement annotation to the generated class and then tell it use the correct element name required when marshalling. e.g GetQuickEstimateRequestContainer element is renamed to the element name that referenced it in the WSDL (GetQuickEstimateRequest) -->
<annox:annotateClass>@javax.xml.bind.annotation.XmlRootElement(name="GetQuickEstimateRequest")</annox:annotateClass>
</jaxb:bindings>
<jaxb:bindings node="xs:complexType[@name='GetQuickEstimateResponseContainer']">
<annox:annotateClass>@javax.xml.bind.annotation.XmlRootElement(name="GetQuickEstimateResponse")</annox:annotateClass>
</jaxb:bindings>
</jaxb:bindings>
</jaxb:bindings>
Созданный класс (GetQuickEstimateRequestContainer.java) с аннотацией @XmlRootElement и правильным именем элемента
@XmlRootElement(name = "GetQuickEstimateRequest")
public class GetQuickEstimateRequestContainer {
...
// class member fields & setters and getters
...
}
Вы пытались изменить свой XSD, как это?
<!-- create-logical-system -->
<xs:element name="methodCall">
<xs:complexType>
...
</xs:complexType>
</xs:element>
Чтобы решить эту проблему, вы должны настроить привязку 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>
Я просто какое-то время боролся с той же проблемой и просто хочу опубликовать свой окончательный результат, который мне подходит. Итак, основные проблемы заключались в следующем:
- Мне нужно сгенерировать строки xml из экземпляров класса JAXB без аннотаций XmlRootElement
- Классам требуются дополнительные классы для привязки к процессу маршаллинга.
Следующий класс отлично подходит для этой проблемы:
public class Object2XmlConverter {
public static <T> String convertToString(final T jaxbInstance, final Class<?>... additionalClasses)
throws JAXBException {
final Class<T> clazz = (Class<T>) jaxbInstance.getClass();
final JAXBContext jaxbContext;
if (additionalClasses.length > 0) {
// this path is only necessary if you need additional classes to be bound
jaxbContext = JAXBContext.newInstance(addClassesToBeBound(clazz, additionalClasses));
} else {
jaxbContext = JAXBContext.newInstance(clazz);
}
final QName qname = new QName("", jaxbInstance.getClass().getSimpleName());
final JAXBElement<T> jaxbElement = new JAXBElement<T>(qname, clazz, null, jaxbInstance);
final Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
final StringWriter stringWriter = new StringWriter();
jaxbMarshaller.marshal(jaxbElement, stringWriter);
return stringWriter.toString();
}
private static <T> Class<?>[] addClassesToBeBound(final Class<T> clazz, final Class<?>[] additionalClasses) {
final Class<?>[] classArray = new Class<?>[additionalClasses.length + 1];
for (int i = 0; i < additionalClasses.length; i++) {
classArray[i] = additionalClasses[i];
}
classArray[classArray.length - 1] = clazz;
return classArray;
}
public static void main(final String[] args) throws Exception {
final Ns1TargetHeaderTyp dataTyp = ...;
System.out.println(convertToString(dataTyp));
}
}