繁体   English   中英

JAXB 没有生成@XmlRootElement

[英]No @XmlRootElement generated by JAXB

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

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

@XmlRootElement存在是因为 JAXB 运行时需要某些信息来编组/解组给定对象,特别是 XML 元素名称和命名空间。 您不能将任何旧对象传递给 Marshaller。 @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);

正如上述答案之一所暗示的那样,如果在 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>

解组不需要@XmlRootElement - 如果使用 Unmarshaller#unmarshall 的 2 参数形式。

所以,如果不是这样做:

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

一个应该做的:

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

后面的代码在 UserType 类级别不需要 @XmlRootElement 注释。

您可以使用How to generate @XmlRootElement Classes for Base Types in 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 的回答(Joe Jun 26 '09 at 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;
  }
}

如您所知,答案是使用 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包装对象。

始终使用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();

万一我对这个问题的经验给了某人一个尤里卡! 片刻..我将添加以下内容:

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

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

我回到 IntellJ 工具,看到“Desgin Type”下拉菜单中的默认选项(我当然不明白……如果我说实话,我仍然不明白)是:

设计类型:

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

我把这个改成

“本地元素/类型”

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

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

使用 Maven 构建,您可以添加@XmlRootElement注释

使用“ jaxb2-basics-annotate ”插件。

查看更多信息:见

配置 Maven 以使用 JAXB 从 XML 模式生成类

JAXB XJC 代码生成

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

你有没有尝试像这样改变你的xsd?

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

所以我使用maven-jaxb2-plugin从一个大而复杂的 WSDL 文件生成类并遇到这个问题。 问题是 WSDL 中的元素将complexType定义引用为type ,因此未生成元素类,并且在尝试使用 complexType 类时它产生了缺少的@XmlRootElement错误。

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

我最终做的是使用第二个 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>

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

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

这个话题很古老,但在企业业务环境中仍然相关。 我尽量避免接触 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>

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

要解决它,您应该在使用 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>

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

  • 我必须从没有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));
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM