没有JAXB生成的@XmlRootElement

我试图从FpML(金融产品标记语言)4.5版本生成Java类。生成了大量代码,但我无法使用它。试图序列化一个简单的文档,我得到这个:

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,然后包括所有类型。

293474 次浏览

这对我们也没用。但我们确实找到了一篇被广泛引用的文章,增加了一些背景……为了方便下一个人,我将在这里链接到它:http://weblogs.java.net/blog/kohsuke/archive/2006/03/why_does_jaxb_p.html

为了将其他人已经陈述或暗示的内容联系在一起,JAXB XJC决定是否将@XmlRootElement注释放在生成的类上的规则不是简单的(查看这篇文章)。

@XmlRootElement的存在是因为JAXB运行时需要某些信息才能封送/解封送给定对象,特别是XML元素名称和名称空间。你不能直接把任何旧对象传递给Marshaller。@XmlRootElement提供了这个信息。

然而,注释只是一种方便,JAXB并不需要它。另一种方法是使用JAXBElement包装器对象,它提供与@XmlRootElement相同的信息,但以对象的形式,而不是注释的形式。

然而,JAXBElement对象很难构造,因为您需要知道XML元素名称和名称空间,而业务逻辑通常不知道。

值得庆幸的是,当XJC生成一个类模型时,它也生成了一个名为ObjectFactory的类。这在一定程度上是为了向后兼容JAXB v1,但它也是XJC放置生成的工厂方法的地方,这些方法在您自己的对象周围创建JAXBElement包装器。它为您处理XML名称和名称空间,因此您无需担心。你只需要浏览ObjectFactory方法(对于大型模式,可能有数百个方法)来找到你需要的方法。

乔的回答(乔09年6月26日17:26)对我来说是这样的。简单的答案是,如果对JAXBElement进行封送,那么缺少@XmlRootElement注释是没有问题的。使我困惑的是生成的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;
}
}

你试过这样改变你的xsd吗?

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

上面已经链接的博客文章底部提到了这一点,但这对我来说就像一种享受:

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不需要解组-如果使用2参数形式的Unmarshaller#unmarshall。

所以,如果不这样做:

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

一个人应该做:

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

后一种代码将不需要UserType类级别的@XmlRootElement注释。

为了解决这个问题,你应该在使用wsimport编译之前配置一个xml绑定,将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>

你可以使用绑定如何为XSD中的基类型生成@XmlRootElement类?来修复这个问题。

下面是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>

在Maven构建中,可以添加@XmlRootElement注释

使用"jaxb2-basics-annotate"插件。

查看更多信息:参见

配置Maven使用JAXB从XML Schema生成类

JAXB XJC代码生成

正如上面的答案之一所暗示的,如果在XSD中根元素的类型定义为命名类型,则根元素上不会有XMLRootElement,因为该命名类型可以在XSD中的其他地方使用。尝试将其转换为匿名类型,即:

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


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

你会:

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

万一我对这个问题的经验给了某人一个顿悟!时刻. .我补充如下:

在使用IntelliJ的“从实例文档中生成xsd”菜单选项生成的xsd文件时,我也遇到了这个问题。

当我接受这个工具的所有默认值时,它生成了一个xsd文件,当与jaxb一起使用时,生成了不带@XmlRootElement的java文件。在运行时,当我试图marshal时,我得到了与这个问题中讨论的相同的异常。

我回到IntellJ工具,在“设计类型”下拉菜单中看到了默认选项(当然我不明白…老实说,我现在也不知道)是:

设计类型:

“局部元素/全局复杂类型”

我把这个改为

“局部元素/类型”

,现在它生成了一个(实质上)不同的xsd,当与jaxb一起使用时产生@XmlRootElement。我不能说我理解它的来龙去脉,但它对我很管用。

如您所知,答案是使用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包装器适用于JAXB不生成@XmlRootElement的情况。这些包装器在maven-jaxb2-plugin生成的ObjectFactory类中可用。如:

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

经过两天的努力,我找到了解决问题的办法。你可以使用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();

这个主题相当古老,但在企业业务上下文中仍然相关。我尽量避免接触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>

它将主要创建用于导入xsd定义的XmlRootElements。

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>

生成器不会捕捉到一个类可能就足够了的事实,因此每次执行都会创建自定义类。这正是我所需要的。

我只是在同一个问题上挣扎了一段时间,只是想发布我的最终结果,这对我来说很好。 所以基本问题是:

  • 我必须从没有XmlRootElement注释的JAXB类实例生成xml字符串
  • 这些类需要为编组过程绑定其他类

下面的类可以很好地解决这个问题:

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

所以我使用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/ estimateservicbbinding .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
...
}