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


当前回答

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

其他回答

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

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

我必须从没有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定义作为类型,因此没有生成元素类,并且在尝试使用complexType类时产生了缺少的@XmlRootElement错误。

在我看来,修改WSDL并不是一个切实可行的解决方案,唯一可行的方法似乎是设计一种在生成过程中添加缺失注释的方法。它还会在编组时引起序列化问题,因为请求发送了错误的元素名称,而且响应中也没有具有匹配元素名称的类。

最后我使用了第二个maven插件jaxb2-basic - annotation,它允许您通过使用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>

生成带有@XmlRootElement注释和正确元素名称的类(GetQuickEstimateRequestContainer.java)

@XmlRootElement(name = "GetQuickEstimateRequest")
public class GetQuickEstimateRequestContainer {
    ...
    // class member fields & setters and getters
    ...
}

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

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

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

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

设计类型:

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

我把这个改为

“local文本/ types”

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

为了将其他人已经陈述或暗示的内容联系在一起,JAXB XJC决定是否将@XmlRootElement注释放在生成的类上的规则非常重要(请参阅本文)。

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

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

但是,JAXBElement对象构造起来很麻烦,因为您需要知道XML元素名称和名称空间,而业务逻辑通常不知道这些。

Thankfully, when XJC generates a class model, it also generates a class called ObjectFactory. This is partly there for backwards compatibility with JAXB v1, but it's also there as a place for XJC to put generated factory methods which create JAXBElement wrappers around your own objects. It handles the XML name and namespace for you, so you don't need to worry about it. You just need to look through the ObjectFactory methods (and for large schema, there can be hundreds of them) to find the one you need.