Aucun @XmlRootElement généré par JAXB

209

J'essaie de générer des classes Java à partir de la version 4.5 du FpML (Finanial Products Markup Language). Une tonne de code est généré, mais je ne peux pas l'utiliser. En essayant de sérialiser un document simple, j'obtiens ceci:

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]

En fait, aucune classe n'a l'annotation @XmlRootElement, alors que puis-je faire de mal?. Je pointe xjc (JAXB 2.1) vers fpml-main-4-5.xsd, qui inclut ensuite tous les types.

robinr
la source

Réponses:

261

Pour lier ce que d'autres ont déjà déclaré ou laissé entendre, les règles selon lesquelles JAXB XJC décide de mettre ou non l' @XmlRootElementannotation sur une classe générée ne sont pas triviales ( voir cet article ).

@XmlRootElementexiste parce que le runtime JAXB nécessite certaines informations afin de marshaler / démarsaler un objet donné, en particulier le nom de l'élément XML et l'espace de noms. Vous ne pouvez pas simplement passer un vieil objet au Marshaller. @XmlRootElementfournit ces informations.

L'annotation n'est cependant qu'une commodité - JAXB n'en a pas besoin. L'alternative à est d'utiliser des JAXBElementobjets wrapper, qui fournissent les mêmes informations que @XmlRootElement, mais sous la forme d'un objet, plutôt que d'une annotation.

Cependant, les JAXBElementobjets sont difficiles à construire, car vous devez connaître le nom de l'élément XML et l'espace de noms, ce que la logique métier ne connaît généralement pas.

Heureusement, lorsque XJC génère un modèle de classe, il génère également une classe appelée ObjectFactory. Ceci est en partie là pour la compatibilité ascendante avec JAXB v1, mais c'est aussi là pour que XJC place des méthodes d'usine générées qui créent des JAXBElementwrappers autour de vos propres objets. Il gère le nom et l'espace de noms XML pour vous, vous n'avez donc pas à vous en préoccuper. Il vous suffit de parcourir les ObjectFactoryméthodes (et pour les grands schémas, il peut y en avoir des centaines) pour trouver celle dont vous avez besoin.

skaffman
la source
15
Solution de cas spécial: quand vous pouvez modifier le xsd utilisé pour la génération de classe: Après avoir lu le lien fourni dans cette réponse, la solution dans mon cas était de modifier le fichier xsd utilisé pour générer les classes: j'ai changé la définition de l'élément racine en un définition intégrée au lieu d'utiliser la référence à un type défini séparément. Cela permet à JAXB de définir cet élément comme @XmlRootElement, ce qui n'était pas possible avec l'élémentType qui était utilisé auparavant pour l'élément racine.
Arthur
2
<scowl> changer l'élément racine pour qu'il soit de type inline, cependant, fait que toutes les classes soient des classes internes du type racine. De plus, même si le type d'élément racine est défini APRÈS l'élément racine lui-même (apparemment autorisé par le schéma), JAXB n'annotera toujours pas avec @XmlRootElement.
Pawel Veselov
10
ie new ObjectFactory().createPositionReport(positionReport)retourneJAXBElement<PositionReport>
vikingsteve
17
Que faire si la méthode ObjectFactory générée ne crée pas une méthode qui encapsule l'argument dans un JXBElement? Dans mon cas, la méthode d'usine est 0-aire et renvoie juste un newobjet. (Pourquoi certaines classes reçoivent-elles des assistants de wrapper JAXBElement et d'autres non?) Je suppose que dans ce cas, nous devons créer le wrapper nous-mêmes?
Carl G
1
@CarlG Je suis dans la même situation - aucun XmlRootElement ni JAXBElement n'apparaissent dans mes classes. Avez-vous trouvé une solution à ce cas?
Mickael Marrache
68

Ceci est mentionné au bas du billet de blog déjà lié ci-dessus, mais cela fonctionne comme un régal pour moi:

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);
Grondin
la source
Je préfère la réponse marquée, mais cela fonctionne aussi pour moi.
Pedro Dusso
1
que jccontient l'extrait ci-dessus?
Arun
3
@ArunRaj c'est la classe
JAXBContext
51

Comme indiqué dans l'une des réponses ci-dessus, vous n'obtiendrez pas de XMLRootElement sur votre élément racine si dans le XSD son type est défini comme un type nommé, car ce type nommé pourrait être utilisé ailleurs dans votre XSD. Essayez de lui donner un type anonyme, c'est-à-dire au lieu de:

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

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

tu aurais:

<xsd:element name="myRootElement">
    <xsd:complexType>
    ...
    <xsd:complexType>
</xsd:element>
Matthew Wise
la source
1
Ce n'est pas vrai pour moi. Mon type est anonyme (incorporé dans mon élément racine) et aucune annotation XmlRootElement n'est générée. Une idée?
Mickael Marrache
38

@XmlRootElement n'est pas nécessaire pour unmarshalling - si l'on utilise la forme à 2 paramètres de Unmarshaller # unmarshall.

Donc, si au lieu de faire:

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

il faut faire:

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

Ce dernier code ne nécessitera pas d'annotation @XmlRootElement au niveau de la classe UserType.

Sayantam
la source
2
Connaissez-vous une manière tout aussi élégante de marshaler un objet qui n'a pas XmlRootElement - sans l'envelopper dans un JAXBElement comme mentionné par skaffman, Gurnard et al?
Chris
4
+1 Fonctionne parfaitement! Une modification pour plus de clarté ... Dans votre solution, 'someSource' est un terme très vague. Pour élaborer: JAXBElement <TargetClazz> root = unmarshaller.unmarshal (nouveau StreamSource (nouveau fichier ("some.xml")), TargetClazz.class);
supernova
4
Poursuite de l'élaboration de «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();
Steve Pitchers
21

La réponse de Joe (Joe 26 juin 09 à 17:26) le fait pour moi. La réponse simple est que l'absence d'une annotation @XmlRootElement ne pose aucun problème si vous marshalez un JAXBElement. La chose qui m'a dérouté est que l'ObjectFactory généré a 2 méthodes createMyRootElement - la première ne prend aucun paramètre et donne l'objet non enveloppé, la seconde prend l'objet non enveloppé et le retourne enveloppé dans un JAXBElement, et la mise en forme que JAXBElement fonctionne bien. Voici le code de base que j'ai utilisé (je suis nouveau dans ce domaine, donc excuses si le code n'est pas formaté correctement dans cette réponse), largement extrait du texte du lien :

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;
  }
}
Yaqoob
la source
2
J'ai un cas où ma classe ObjectFactory ne définit que des méthodes qui retournent des instances régulières et non des instances JAXBElement ...
Mickael Marrache
20

Vous pouvez résoudre ce problème en utilisant la liaison de Comment générer des classes @XmlRootElement pour les types de base dans XSD? .

Voici un exemple avec 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>

Voici le binding.xjbcontenu du fichier

<?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
la source
3
En effet, l'utilisation de <xjc: simple> dans le fichier binding.xjb a fait l'affaire. Solution géniale si vous ne voulez pas changer votre code de marshaling ou votre WSDL. Notez que xjc: simple génère différents noms de méthode (pluriel) pour les récupérateurs de collection (getOrders au lieu de getOrder par exemple)
dvtoever
10

Comme vous le savez, la réponse consiste à utiliser ObjectFactory (). Voici un exemple du code qui a fonctionné pour moi :)

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
la source
à votre point ... comment utiliser les méthodes JAXBElement <?> create ... () d'ObjectFactory pour les éléments imbriqués? c'est-à-dire: <SOAP-ENV: Header> <wsse: Security> <wsse: UsernameToken> </ wsse: UsernameToken> </ wsse: Security> </ SOAP-ENV: Header> J'obtiens: "impossible de trier le type" UsernameTokenType " en tant qu'élément car il manque une annotation @XmlRootElement "
Angelina
6

Cela ne fonctionne pas non plus pour nous. Mais nous avons trouvé un article largement cité qui ajoute QUELQUES antécédents ... Je vais le lier ici pour le bien de la prochaine personne: http://weblogs.java.net/blog/kohsuke/archive/2006/03 /why_does_jaxb_p.html

mcherm
la source
Cela a bien fonctionné pour moi, merci. J'ai également constaté que je rassemblais le mauvais objet JAXB (pas la racine comme je le pensais) pendant le processus. J'ai oublié de créer un JAXBElement et j'essayais de marshaler uniquement l'objet renvoyé de la classe ObjectFactory que j'avais obtenu de la liaison. Cela a essentiellement résolu le problème (au cas où quelqu'un d'autre se heurterait au même problème).
Joe Bane
1
404: "Nous sommes désolés que le site java.net soit fermé. La plupart des projets Open Source précédemment hébergés sur java.net ont été déplacés."
Tristan
6

Après avoir traîné pendant deux jours, j'ai trouvé la solution au problème. Vous pouvez utiliser la classe ObjectFactory pour contourner les classes qui n'ont pas @XmlRootElement . ObjectFactory a surchargé les méthodes pour l'enrouler autour de JAXBElement.

Méthode: 1 fait la création simple de l'objet.

Méthode: 2 enveloppera l'objet avec @JAXBElement .

Utilisez toujours la méthode: 2 pour éviter javax.xml.bind.MarshalException - avec une exception liée manquant une annotation @XmlRootElement.

Veuillez trouver l'exemple de code ci-dessous

Méthode: 1 fait la création simple de l'objet

public GetCountry createGetCountry() {
        return new GetCountry();
    }

Méthode: 2 enveloppera l'objet avec @JAXBElement .

 @XmlElementDecl(namespace = "my/name/space", name = "getCountry")
 public JAXBElement<GetCountry> createGetCountry(GetCountry value) {
        return new JAXBElement<GetCountry>(_GetCountry_QNAME, GetCountry.class, null, value);
    }

Exemple de code de travail:

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
la source
Merci d'avoir donné la référence du code avec le modèle de service Web de printemps, car j'avais du mal à le comprendre pendant un certain temps!
RRR_J
5

Dans le cas où mon expérience de ce problème donne à quelqu'un un Eureka! moment .. je vais ajouter ce qui suit:

J'obtenais également ce problème, lorsque j'utilisais un fichier xsd que j'avais généré en utilisant l'option de menu "Générer xsd à partir d'un document d'instance" d'IntelliJ.

Lorsque j'ai accepté tous les paramètres par défaut de cet outil, il a généré un fichier xsd qui, lorsqu'il est utilisé avec jaxb, génère des fichiers java sans @XmlRootElement. Au moment de l'exécution, lorsque j'ai essayé de rassembler, j'ai eu la même exception que celle discutée dans cette question.

Je suis retourné à l'outil IntellJ et j'ai vu l'option par défaut dans la liste déroulante "Type de Desgin" (ce que bien sûr je ne comprenais pas .. et toujours pas si je suis honnête) était:

Type de conception:

"éléments locaux / types complexes globaux"

J'ai changé cela en

"éléments / types locaux"

, maintenant il a généré un xsd (sensiblement) différent, qui a produit le @XmlRootElementlorsqu'il est utilisé avec jaxb. Je ne peux pas dire que je comprends les avantages et les inconvénients, mais cela a fonctionné pour moi.

johnm
la source
4

Les wrappers JAXBElement fonctionnent pour les cas où aucun @XmlRootElementn'est généré par JAXB. Ces wrappers sont disponibles dans la ObjectFactoryclasse générée par maven-jaxb2-plugin. Par exemple:

     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
la source
3

Avez-vous essayé de changer votre xsd comme ça?

<!-- create-logical-system -->
<xs:element name="methodCall">
  <xs:complexType>
    ...
  </xs:complexType>
</xs:element>
Tony
la source
Cela a fonctionné pour moi avec JDK 1.7u71. Un élément de niveau supérieur obtient le @XmlRootElement par xjc. Au départ, je n'avais qu'un type complexe de haut niveau. Devoir envelopper dans un JAXBElement est tout simplement laid.
Serge Merzliakov
1

Pour le résoudre, vous devez configurer une liaison xml avant de compiler avec wsimport, en définissant generateElementProperty sur 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
la source
l'étiquette d'emballage devrait être<jaxb:bindings> ... <jaxws:bindings> ... </jaxws:bindings> ... </jaxb:bindings>
aliopi
0

Le sujet est assez ancien mais toujours pertinent dans les contextes d'entreprise. J'ai essayé d'éviter de toucher les xsds afin de les mettre à jour facilement à l'avenir. Voici mes solutions ..

1. Surtout xjc:simplesuffit

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

Il créera principalement des XmlRootElements pour importer des définitions xsd.

2. Divisez vos jaxb2-maven-pluginexécutions

J'ai rencontré que cela fait une énorme différence si vous essayez de générer des classes à partir de plusieurs définitions xsd au lieu d'une définition d'exécution par xsd.

Donc, si vous avez une définition avec plusieurs <source>, essayez simplement de les diviser:

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

Le générateur n'acceptera pas le fait qu'une classe pourrait être suffisante et créera donc des classes personnalisées par exécution. Et c'est exactement ce dont j'ai besoin;).

judomu
la source