Nenhum @XmlRootElement gerado pelo JAXB


209

Estou tentando gerar classes Java a partir da versão 4.5 do FpML (Finanial Products Markup Language). Uma tonelada de código é gerada, mas não posso usá-lo. Tentando serializar um documento simples, recebo o seguinte:

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]

De fato, nenhuma classe tem a anotação @XmlRootElement, então o que posso fazer de errado? Estou apontando o xjc (JAXB 2.1) para fpml-main-4-5.xsd, que inclui todos os tipos.

Respostas:


261

Para unir o que outros já declararam ou sugeriram, as regras pelas quais o JAXB XJC decide se deve ou não colocar a @XmlRootElementanotação em uma classe gerada não são triviais ( consulte este artigo ).

@XmlRootElementexiste porque o tempo de execução JAXB requer determinadas informações para organizar / remover o compartilhamento de um determinado objeto, especificamente o nome do elemento XML e o espaço de nomes. Você não pode simplesmente passar nenhum objeto antigo para o Marshaller. @XmlRootElementfornece essas informações.

A anotação é apenas uma conveniência, no entanto - o JAXB não exige isso. A alternativa é usar JAXBElementobjetos wrapper, que fornecem as mesmas informações @XmlRootElement, mas na forma de um objeto, em vez de uma anotação.

No entanto, os JAXBElementobjetos são difíceis de construir, pois você precisa saber o nome do elemento XML e o espaço para nome, o que a lógica comercial geralmente não tem.

Felizmente, quando o XJC gera um modelo de classe, ele também gera uma classe chamada ObjectFactory . Isso está parcialmente disponível para compatibilidade com o JAXB v1, mas também como um local para o XJC colocar métodos de fábrica gerados que criam JAXBElementwrappers em torno de seus próprios objetos. Ele lida com o nome XML e o namespace para você, para que você não precise se preocupar com isso. Você só precisa examinar os ObjectFactorymétodos (e para esquemas grandes, pode haver centenas deles) para encontrar o que você precisa.


15
Solução de caso especial: quando você pode modificar o xsd usado para geração de classe: Depois de ler o link fornecido nesta resposta, a solução no meu caso foi modificar o arquivo xsd usado para gerar as classes: alterei a definição do elemento raiz para um definição embutida em vez de usar a referência a um tipo definido separadamente. Isso permite que o JAXB configure esse elemento como @XmlRootElement, o que não era possível com o elementType que foi usado anteriormente para o elemento raiz.
Arthur

2
<scowl> alterar o elemento raiz para ser do tipo embutido, no entanto, faz com que todas as classes sejam classes internas do tipo raiz. Além disso, mesmo que o tipo de elemento raiz seja definido APÓS o próprio elemento raiz (aparentemente permitido pelo esquema), o JAXB ainda não fará anotações com @XmlRootElement.
Pawel Veselov

10
ou seja, new ObjectFactory().createPositionReport(positionReport)retornaJAXBElement<PositionReport>
vikingsteve 27/09/13

17
E se o método ObjectFactory gerado não criar um método que agrupe o argumento em um JXBElement? No meu caso, o método de fábrica é 0-arity e apenas retorna um newobjeto. (Por que algumas classes recebem ajudantes do wrapper JAXBElement e outras não?) Acho que nesse caso devemos criar o wrapper por conta própria?
Carl G

1
@CarlG Estou na mesma situação - nenhum XmlRootElement nem JAXBElement aparece nas minhas classes. Você encontrou uma solução para este caso?
Mickael Marrache

68

Isso é mencionado na parte inferior da postagem do blog já vinculada acima, mas funciona como um deleite para mim:

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

Eu prefiro a resposta marcada, mas isso funciona para mim também.
Pedro Dusso

1
o que há jcno snippet acima?
Arun

3
@ArunRaj é a classe JAXBContext
bacamarte

51

Conforme sugerido em uma das respostas acima, você não receberá um XMLRootElement em seu elemento raiz se no XSD seu tipo for definido como um tipo nomeado, pois esse tipo nomeado poderá ser usado em outro lugar no seu XSD. Tente definir um tipo anônimo, ou seja, em vez de:

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

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

você teria:

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

1
Isso não é verdade para mim. Meu tipo é anônimo (incorporado ao meu elemento raiz) e nenhuma anotação XmlRootElement é gerada. Qualquer ideia?
Mickael Marrache

38

@XmlRootElement não é necessário para desserializar - se alguém usar o formulário de 2 parâmetros de Unmarshaller # unmarshall.

Então, se em vez de fazer:

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

deve-se fazer:

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

O último código não exigirá a anotação @XmlRootElement no nível da classe UserType.


2
Você conhece uma maneira igualmente elegante de organizar um objeto que não possui XmlRootElement - sem agrupá-lo em um JAXBElement conforme mencionado por skaffman, Gurnard et al?
Chris

4
+1 funciona perfeitamente! Uma edição para mais clareza ... Na sua solução, 'someSource' é um termo muito vago. Para elaborar: JAXBElement <TargetClazz> root = unmarshaller.unmarshal (novo StreamSource (novo arquivo ("algum.xml"))), TargetClazz.class);
Supernova

4
Elaboração adicional 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

A resposta de Joe (Joe 26/06/2009 às 17:26) faz isso por mim. A resposta simples é que a ausência de uma anotação @XmlRootElement não é problema se você empacotar um JAXBElement. O que me confundiu é que o ObjectFactory gerado possui 2 métodos createMyRootElement - o primeiro não aceita parâmetros e fornece o objeto desembrulhado, o segundo pega o objeto desembrulhado e o devolve em um JAXBElement e indica que o JAXBElement funciona bem. Aqui está o código básico que eu usei (sou novo nisso, desculpe-me se o código não estiver formatado corretamente nesta resposta), amplamente extraído do texto do link :

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

2
Eu tenho um caso onde minha classe ObjectFactory define apenas métodos que retornam instâncias regulares e instâncias não JAXBElement ...
Mickael Marrache

20

Você pode corrigir esse problema usando a ligação de Como gerar classes @XmlRootElement para tipos de base no XSD? .

Aqui está um exemplo com o 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>

Aqui está o binding.xjbconteúdo do arquivo

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

3
De fato, usar <xjc: simple> no arquivo binding.xjb fez o truque. Solução incrível se você não deseja alterar seu código de empacotamento ou seu WSDL. Note-se que xjc: simples gera diferentes nomes de métodos (plural) para getters de coleta (GetOrders em vez de GetOrder por exemplo)
dvtoever

10

Como você sabe a resposta é usar o ObjectFactory (). Aqui está uma amostra do código que funcionou para mim :)

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

ao seu ponto ... como uso métodos JAXBElement <?> create ... () do ObjectFactory para elementos aninhados? ou seja: <SOAP-ENV: cabeçalho> <wsse: Segurança> <wsse: Nome de usuário> </ wsse: Nome de usuário> </ wsse: Segurança> </ SOAP-ENV: cabeçalho> recebo: "não é possível empacotar o tipo" UsernameTokenType " como um elemento porque está faltando uma anotação @XmlRootElement"
Angelina

6

Também não está funcionando para nós. Mas encontramos um artigo amplamente citado que adiciona ALGUNS antecedentes ... Vou colocar um link aqui para o bem da próxima pessoa: http://weblogs.java.net/blog/kohsuke/archive/2006/03 /why_does_jaxb_p.html


Isso funcionou bem para mim, obrigado. Também descobri que estava organizando o objeto JAXB errado (não a raiz como eu pensava) no processo de passar por isso. Esqueci de criar um JAXBElement e estava tentando empacotar apenas o objeto retornado da classe ObjectFactory que obtive da ligação. Isso basicamente resolveu o problema completamente (no caso de alguém encontrar o mesmo problema).
218 Joe Bane

1
404: "Lamentamos, mas o site java.net foi fechado. A maioria dos projetos de código aberto anteriormente hospedados em java.net foram realocados."
Tristan


6

Depois de fazer o manuseio por dois dias, encontrei a solução para o problema. Você pode usar a classe ObjectFactory para solucionar as classes que não possuem o @XmlRootElement . O ObjectFactory sobrecarregou métodos para envolvê-lo em torno do JAXBElement.

Método 1 faz a criação simples do objeto.

Método: 2 envolverá o objeto com @JAXBElement .

Sempre use o método: 2 para evitar javax.xml.bind.MarshalException - com a exceção vinculada ausente na anotação @XmlRootElement.

Encontre o código de exemplo abaixo

Método: 1 faz a criação simples do objeto

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

Método: 2 envolverá o objeto com @JAXBElement .

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

Exemplo de código de trabalho:

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

Obrigado por fornecer a referência de código com o modelo Spring WebService, pois estava lutando para descobrir isso há algum tempo!
RRR_J

5

Caso minha experiência com esse problema dê a alguém um Eureka! momento .. vou adicionar o seguinte:

Eu também estava tendo esse problema ao usar um arquivo xsd que eu havia gerado usando a opção de menu "Gerar xsd a partir do documento da instância" do IntelliJ.

Quando aceitei todos os padrões desta ferramenta, ela gerou um arquivo xsd que, quando usado com jaxb, gerava arquivos java sem @XmlRootElement . No tempo de execução, quando tentei empacotar, recebi a mesma exceção discutida nesta pergunta.

Voltei à ferramenta IntellJ e vi a opção padrão no menu suspenso "Desgin Type" (que é claro que eu não entendi ... e ainda não sei se sou honesto):

Tipo Desgin:

"elementos locais / tipos complexos globais"

Eu mudei isso para

"elementos / tipos locais"

, agora gerou um xsd (substancialmente) diferente, que produziu o @XmlRootElementquando usado com o jaxb. Não posso dizer que entendo as vantagens e desvantagens, mas funcionou para mim.



4

Os wrappers JAXBElement funcionam para casos em que nenhum @XmlRootElementé gerado pelo JAXB. Esses wrappers estão disponíveis na ObjectFactoryclasse gerada por maven-jaxb2-plugin. Por exemplo:

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

3

Você tentou mudar seu xsd assim?

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

Isso funcionou para mim com o JDK 1.7u71. Um elemento de nível superior é atribuído a @XmlRootElement por xjc. Inicialmente, eu tinha apenas um tipo complexo de nível superior. Ter que quebrar um JAXBElement é muito feio.
Serge Merzliakov

1

Para resolvê-lo, você deve configurar uma ligação xml antes para compilar com wsimport, configurando generateElementProperty como 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>

a etiqueta de embalagem deve ser<jaxb:bindings> ... <jaxws:bindings> ... </jaxws:bindings> ... </jaxb:bindings>
aliopi

0

O tópico é bastante antigo, mas ainda é relevante nos contextos de negócios corporativos. Tentei evitar tocar nos xsds para atualizá-los facilmente no futuro. Aqui estão minhas soluções ..

1. Principalmente xjc:simpleé suficiente

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

Ele criará principalmente XmlRootElements para importar definições xsd.

2. Divida suas jaxb2-maven-pluginexecuções

Descobri que isso faz uma enorme diferença se você tentar gerar classes a partir de várias definições xsd em vez de uma definição de execução por xsd.

Portanto, se você tem uma definição com múltiplos <source>, tente apenas dividi-los:

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

O gerador não perceberá o fato de que uma classe pode ser suficiente e, portanto, criará classes personalizadas por execução. E é exatamente isso que eu preciso;).

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.