XML – Trabalhando com JAXB

Java Architecture for XML Binding (JAXB) é uma biblioteca Java que possibilita a conversão de arquivo XML em objetos Java ou vice versa. Também fornece ferramentas para geração de arquivos XSD e de validação de XML. Outra característica do JAXB é a possibilidade de geração de classes Java através de um arquivo XSD.

O JAXB é de fácil configuração, que é realizada através de anotações nativas a partir do JDK/JRE 6. Neste tutorial vamos ver alguns princípios básicos para salvar, ler e validar arquivos XML.

Vamos conhecer as principais anotações do JAXB:

  • @XmlRootElement – essa anotação indica que o valor da classe será representado como um elemento XML principal;
  • @XmlAccessorType – indica que as anotações estão ou nos atributos ou nos métodos da classe;
  • @XmlType – indica que essa classe na verdade mapeia um tipo de informação específica;
  • @XmlElement – usada nos atributos ou métodos. Indica que o atributo será uma tag do xml;
  • @XmlElementWrapper – mapeia um objeto do tipo lista;
  • @XmlAttribute – mapeia o valor de um campo como atributo no xml.

1. Classe do Projeto

As classes que serão usadas no projeto estão descritas nas Listagens 12 e 3. Estas classes estão anotadas com as anotações referentes ao JAXB e devem ser importadas do pacote javax.xml.bind.annotation.

Listagem 1. Classes Contato
package com.mballem.tutorialArquivos.xmlJaxb;

import javax.xml.bind.annotation.*;
import java.util.Collection;

@XmlRootElement(name = "Contato")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(
        name = "Contato",
        propOrder = {"id", "nome", "email", "endereco", "telefones"},
        namespace = "https://mballem.com/"
)
public class Contato {
    @XmlElement(name = "id", required = true)
    private int id;

    @XmlElement(name = "nome", required = true)
    private String nome;

    @XmlElement(name = "email", required = true)
    private String email;

    @XmlElementWrapper(name = "Telefones")
    @XmlElement(name = "Telefone")
    private Collection; telefones;

    @XmlElement(name = "Endereco", required = true)
    private Endereco endereco;

    //Gerar métodos get/set

    @Override
    public String toString() {
        return "Contato{" +
                "id=" + id +
                ", nome='" + nome + '\'' +
                ", email='" + email + '\'' +
                ", endereco=" + endereco +
                ", telefones=" + telefones +
                '}';
    }
}
Listagem 2. Classe Endereco
package com.mballem.tutorialArquivos.xmlJaxb;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Endereco")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(namespace = "https://mballem.com/")
public class Endereco {
    @XmlElement(name = "id", required = true)
    private int id;

    @XmlElement(name = "logradouro", required = true)
    private String logradouro;

    @XmlElement(name = "bairro", required = true)
    private String bairro;

    @XmlElement(name = "cep", required = true)
    private String cep;

    @XmlElement(name = "cidade", required = true)
    private String cidade;

    @XmlElement(name = "complemento", required = false)
    private String complemento;

    @XmlElement(name = "numero", required = true)
    private int numero;

    //Gerar métodos get/set
        return id;
    }

    @Override
    public String toString() {
        return "Endereco{" +
                "id=" + id +
                ", logradouro='" + logradouro + '\'' +
                ", bairro='" + bairro + '\'' +
                ", cep='" + cep + '\'' +
                ", cidade='" + cidade + '\'' +
                ", complemento='" + complemento + '\'' +
                ", numero=" + numero +
                '}';
    }
}
Listagem 3. Classe Telefones
package com.mballem.tutorialArquivos.xmlJaxb;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "Telefone")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(namespace = "https://mballem.com/")
public class Telefone {
    @XmlElement(name = "id", required = true)
    private int id;

    @XmlElement(name = "ddd", required = true)
    private int ddd;

    @XmlElement(name = "numero", required = true)
    private int numero;

    //Gerar métodos get/set

    @Override
    public String toString() {
        return "Telefone{" +
                "id=" + id +
                ", ddd=" + ddd +
                ", numero=" + numero +
                '}';
    }
}

2. Estrutura do XML

A estrutura do XML gerado será conforme o XML da Figura 1. Uma tag raiz que será a tag <Contato>, e duas tags filhas <Telefones> e <Endereco>. A tag <Telefones> é uma lista de telefones, então conterá como tags filhas o número de telefones adicionados a um contato e cada tag filha será chamada de <Telefone>.

XML

Figura 1 – XML

3. Gerando e Lendo XML com JAXB

O processo usado para transformar um objeto Java em XML é chamado de Marshal e o inverso é chamado de Unmarshal. Para transformar um objeto em XML precisamos instanciar um objeto da classe JAXBContext, e esse “context” é quem fornecerá o Marshaller ou um Unmarshaller. O Marshaller e Unmarshaller são interfaces do pacote javax.xml.bind e responsáveis em fazer a conversão Objeto/XML ou XML/Objeto.

Na Listagem 4 vamos criar um método que irá gerar um XML e transformá-lo em uma String, a qual será impressa no console no final do processo.

Listagem 4. Método marshall() e classe TesteJaxB2.
package com.mballem.tutorialArquivos.xmlJaxb;

import javax.xml.bind.*;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;

public class TesteJaxB2 {

    public static void main(String[] args) {
        Endereco end = new Endereco();
        end.setLogradouro("Rua Venancio Aires");
        end.setNumero(654);
        end.setComplemento("Ap.03A");
        end.setBairro("Centro");
        end.setCidade("Santa Maria");
        end.setCep("97050-100");
        end.setId(1);

        Collection collection = new ArrayList();

        Telefone f1 = new Telefone();
        f1.setId(1);
        f1.setDdd(55);
        f1.setNumero(21210022);
        collection.add(f1);

        Telefone f2 = new Telefone();
        f2.setId(2);
        f2.setDdd(54);
        f2.setNumero(91910022);
        collection.add(f2);

        Contato contato = new Contato();
        contato.setId(1);
        contato.setNome("Ana Maria");
        contato.setEmail("ana.maria@email.com");
        contato.setEndereco(end);
        contato.setTelefones(collection);

         System.out.println(new TesteJaxB2().marshal(contato));
   }

    /**
     * Converte o objeto em uma String com estrutura XML.
     * @param object objeto a ser convertido em XML.
     * @return String contendo a estrutura XML.
     */
    public String marshal(Object object) {
        final StringWriter out = new StringWriter();
        JAXBContext context = null;
        try {
            context = JAXBContext.newInstance(object.getClass());
            marshaller = context.createMarshaller();
            marshaller.setProperty(
                    javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT,
                    Boolean.TRUE
            );
            marshaller.marshal(object, new StreamResult(out));
        } catch (PropertyException e) {
            e.printStackTrace();
        } catch (JAXBException e) {
            e.printStackTrace();
        }
        return out.toString();
    }
}

O resultado exibido no console será o apresentado na Listagem 5.

Listagem 5. XML apresentado no console como resultado do parser.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Contato>
    <id>1</id>
    <nome>Ana Maria</nome>
    <email>ana.maria@email.com</email>
    <Telefones>
        <Telefone>
            <id>1</id>
            <ddd>55</ddd>
            <numero>21210022</numero>
        </Telefone>
        <Telefone>
            <id>2</id>
            <ddd>54</ddd>
            <numero>91910022</numero>
        </Telefone>
    </Telefones>
    <Endereco>
        <id>1</id>
        <logradouro>Rua Venancio Aires</logradouro>
        <bairro>Centro</bairro>
        <cep>97050-100</cep>
        <cidade>Santa Maria</cidade>
        <complemento>Ap.03A</complemento>
        <numero>654</numero>
    </Endereco>
</Contato>

Para salvar o XML gerado em um arquivo XML, vamos usar o método, marshalToFile(Object object, String fileName) da Listagem 6. Este método possui dois par metros, o primeiro é o objeto a ser convertido e o segundo é o destino e o nome do arquivo XML a ser gerado. Inclua no método main() uma chamada ao método marshalToFile():

new TesteJaxB2().marshalToFile(contato, "C:\\contato-jaxb.xml");

Após executá-lo, vá até o diretório destino e confira se o arquivo foi gerado.

Listagem 6. Método marshalToFile().
 /**
     * Converte o objeto em uma estrutura XML.
     * @param object objeto a ser convertido em XML.
     * @param fileName nome do arquivo XML a ser gerado.
     * @return uma string com o conteudo do XML gerado.
     */
    public String marshalToFile(Object object, String fileName) {
        final StringWriter out = new StringWriter();
        JAXBContext context = null;
        try {
            context = JAXBContext.newInstance(object.getClass());
            marshaller = context.createMarshaller();
            marshaller.setProperty(
                    javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT,
                    Boolean.TRUE
            );
            marshaller.marshal(object, new StreamResult(out));
        } catch (PropertyException e) {
            e.printStackTrace();
        } catch (JAXBException e) {
            e.printStackTrace();
        }

        Writer writer = null;
        try {
            writer = new FileWriter(fileName);
            marshaller.marshal(object, writer);
        } catch (JAXBException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (Exception e) {
                e.getMessage();
            }
        }

        return out.toString();
    }

Agora que sabemos transformar objetos em XML com JAXB, vamos fazer o contrário, transformar XML em objetos Java. Veja na Listagem 7 que o código é muito parecido com os anteriores, mas desta vez usamos a interface Unmarshaller. No método main() utilize o seguinte código para recuperar os dados do XML:

String xml =
            "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
            "<Contato>" +
            "    <id>1</id>" +
            "    <nome>Ana Maria</nome>" +
            "    <email>ana.maria@email.com</email>" +
            "    <Endereco>" +
            "        <id>1</id>" +
            "        <logradouro>Rua Venancio Aires</logradouro>" +
            "        <bairro>Centro</bairro>" +
            "        <cep>97050-100</cep>" +
            "        <cidade>Santa Maria</cidade>" +
            "        <complemento>Ap.03A</complemento>" +
            "        <numero>654</numero>" +
            "    </Endereco>" +
            "    <Telefones>" +
            "        <Telefone>" +
            "            <id>1</id>" +
            "            <ddd>55</ddd>" +
            "            <numero>21210022</numero>" +
            "        </Telefone>" +
            "        <Telefone>" +
            "            <id>2</id>" +
            "            <ddd>54</ddd>" +
            "            <numero>91910022</numero>" +
            "        </Telefone>" +
            "    </Telefones>" +
            "</Contato>";

O primeiro parametro do método unmarshal() é o tipo de objeto que será recuperado e o segundo será uma String com o conteúdo XML.

Listagem 7. Recuperando o objeto de uma string com conteúdo XML.
/**
     * Converte um string com estrutura XML em um objeto.
     * @param clazz classe referente ao tipo do objeto a ser retornado.
     * @param stringXml string com o conteudo XML a ser convertido em objeto.
     * @return retorna um novo objeto de clazz.
     */
    public Object unmarshal(Class clazz, String stringXml) {
        JAXBContext context = null;
        try {
            context = JAXBContext.newInstance(clazz);
            unmarshaller = context.createUnmarshaller();
            return unmarshaller.unmarshal(
                    new StreamSource(new StringReader(stringXml))
            );
        } catch (JAXBException e) {
            e.printStackTrace();
        }
        return null;
    }

Para recuperar o conteúdo de um arquivo .xml, vamos usar o método unmarshalFromFile() da Listagem 8. Adicione no método main() o seguinte código:

Contato c2 = (Contato) new TesteJaxB2().unmarshalFromFile(
                Contato.class,
                "C:\\contato-jaxb.xml"
);
System.out.println(c2.toString());

Neste método passamos como par metro a classe do objeto que vamos recuperar e o local com o nome do arquivo.

Listagem 8. Recuperando o objeto de um arquivo do tipo .xml.
/**
     * Realiza a conversao (unmarshal) de um arquivo XML em um objeto do seu tipo.
     * @param clazz classe referente ao objeto a ser criado a partir do XML.
     * @param fileXml nome do arquivo XML a ser convertido em objeto.
     * @return novo objeto.
     */
    public Object unmarshalFromFile(Class clazz, String fileXml) {
        JAXBContext context = null;
        try {
            context = JAXBContext.newInstance(clazz);
            unmarshaller = context.createUnmarshaller();
            return unmarshaller.unmarshal(
                    new FileInputStream(fileXml)
            );
        } catch (JAXBException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

4. Trabalhando com arquivo XSD

O arquivo XSD é um schema, ele define quais são as regras que a estrutura do XML deve seguir, possibilitando a validação desse XML. Esse arquivo é importante para se saber qual o tipo de conteúdo que faz parte do XML. Ele informa por exemplo, se uma tag do arquivo possui dados do tipo String, Double, Integer…, como também se o campo é obrigatório ou não, ou o tamanho mínimo e máximo da informação contidas no campo.

Para gerar um arquivo XSD a partir das suas classes Java, anotadas pelo JAXB, você deve abrir o console, ir até o diretório do seu código fonte e digitar o comando ‘schemagen’ mais os arquivos ‘.java’ que irão gerar o XSD. Veja na Listagem 9 o XSD referente ao projeto apresentado e, na Figura 2 o comando a ser executado.

Figura 2 ? Comando schemagen

Figura 2 – Comando schemagen

Listagem 9. XSD do projeto apresentado.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" targetNamespace="https://mballem.com/"
xmlns:tns="https://mballem.com/"
xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:import schemaLocation="schema2.xsd"/>

  <xs:complexType name="Contato">
    <xs:sequence>
      <xs:element name="id" type="xs:int"/>
      <xs:element name="nome" type="xs:string"/>
      <xs:element name="email" type="xs:string"/>
      <xs:element name="Telefones" minOccurs="0">
        <xs:complexType>
          <xs:sequence>
            <xs:element ref="Telefone" minOccurs="0" maxOccurs="unbounded"/>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
      <xs:element ref="Endereco"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="telefone">
    <xs:sequence>
      <xs:element name="id" type="xs:int"/>
      <xs:element name="ddd" type="xs:int"/>
      <xs:element name="numero" type="xs:int"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="endereco">
    <xs:sequence>
      <xs:element name="id" type="xs:int"/>
      <xs:element name="logradouro" type="xs:string"/>
      <xs:element name="bairro" type="xs:string"/>
      <xs:element name="cep" type="xs:string"/>
      <xs:element name="cidade" type="xs:string"/>
      <xs:element name="complemento" type="xs:string" minOccurs="0"/>
      <xs:element name="numero" type="xs:int"/>
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Os arquivos schema1.xsd e schema2.xsd serão salvos no diretório dos arquivos .java. Também serão gerados arquivos .class das classes usadas. Estes arquivos .class podem ser deletados sem problema. Agora que temos um XSD de exemplo, ele poderá ser usado para validação do XML. Para isso, vamos criar 2 novas classes, a Validator – Listagem 10 – que terá o método de validação e a classe ValidatorErrorHandler – Listagem 11 – para captura de erros no processo de validação.

Listagem 10. Classe Validator.
package com.mballem.tutorialArquivos.xmlJaxb;

import org.xml.sax.SAXException;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.util.JAXBSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.File;
import java.io.IOException;

public class Validator {

    /**
     * Realiza a validacao do objeto com o arquivo XSD.
     * @param xsdFile nome do arquivo XSD.
     * @param object objeto a ser validado.
     */
    public void validator(String xsdFile, Object object) {
        try {

            JAXBContext jc = JAXBContext.newInstance(object.getClass());
            JAXBSource source = new JAXBSource(jc, object);

            SchemaFactory sf = SchemaFactory.newInstance(
                    XMLConstants.W3C_XML_SCHEMA_NS_URI
            );
            Schema schema = sf.newSchema(new File(xsdFile));

            javax.xml.validation.Validator validator = schema.newValidator();
            validator.setErrorHandler(new ValidatorErrorHandler());
            validator.validate(source);

            System.out.println("Successful validation!");
        } catch (SAXException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }
}
Listagem 11. Classe ValidatorErrorHandler.
package com.mballem.tutorialArquivos.xmlJaxb;

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public class ValidatorErrorHandler implements ErrorHandler {

    public void warning(SAXParseException exception) throws SAXException {
        System.out.println("\nWARNING");
        exception.printStackTrace();
    }

    public void error(SAXParseException exception) throws SAXException {
        System.out.println("\nERROR");
        exception.printStackTrace();
    }

    public void fatalError(SAXParseException exception) throws SAXException {
        System.out.println("\nFATAL ERROR");
        exception.printStackTrace();
    }
}

Insira no método main() as instruções:

String path = "C:\\TutorialArquivos\\src\\br\\mb\\tutorialArquivos\\xmlJaxb\\"; 

String file = "schema1.xsd";

new Validator().validator(path + file, contato);

O primeiro parametro é o diretório que contém o arquivo, seguido do nome do arquivo. O segundo parametro será o objeto que será validado pelo arquivo. Se a validação for bem sucedida, será exibida a mensagem “Successful validation!”, caso contrário, um erro será informado e você deverá adequar seu objeto as regras do arquivo XSD. Um erro pode acontecer quando, por exemplo, um campo é obrigatório mas no objeto ele não foi inserido. Ou então, quando um campo é do tipo String, mas no objeto ele está como Double, entre outros erros.

Outro recurso que poderá ser usado é o processo inverso, ao invés de gerar um XSD através das classes, gerar classes através de um arquivo XSD. Observe na Figura 3 o comando executado para tal ação. Primeiro foi criado um diretório chamado gerar_xsd e copiado para dentro dele os arquivos schema1.xsd e schema2.xsd, gerados anteriormente. Entre no diretório pela linha de comando e execute o comando xjc seguido do nome do arquivo schema1.xsd. O parametro -p é para a criação dos pacotes onde as classes serão geradas.

Figura 3 ? Comando xjc

Figura 3 – Comando xjc

Além das classes ContatoEndereco e Telefone, ele gera também uma classe chamada ObjectFactory. A classe ObjectFactory é responsável por criar uma inst ncia do JAXBElement apropriada para o tipo de objeto a ser serializado, a qual pode ser usada para geração de XML’s. Já, para popular o objeto Java, com dados de um XML precisamos de um JAXBContext, porém agora temos que pegar um objeto Unmarshaller. O Unmarshaller recebe um arquivo XML e devolve um JAXBElement contendo um objeto populado – veja exemplo na Listagem 12.

Listagem 12. Classe TestJaxbXSD.
package br.com.tutorial.jaxb;

import javax.xml.bind.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;

public class TestJaxbXSD {
    public static void main(String[] args) {
        Endereco end = new Endereco();
        end.setLogradouro("Rua Venancio Aires");
        end.setNumero(654);
        end.setComplemento("Ap.03A");
        end.setBairro("Centro");
        end.setCidade("Santa Maria");
        end.setCep("97050-100");
        end.setId(1);

        Collection collection = new ArrayList();

        Telefone f1 = new Telefone();
        f1.setId(1);
        f1.setDdd(55);
        f1.setNumero(21210022);
        collection.add(f1);

        Telefone f2 = new Telefone();
        f2.setId(2);
        f2.setDdd(54);
        f2.setNumero(91910022);
        collection.add(f2);

        Contato.Telefones telefones = new Contato.Telefones();
        telefones.getTelefone().addAll(collection);

        Contato contato = new Contato();
        contato.setId(1);
        contato.setNome("Ana Maria");
        contato.setEmail("ana.maria@email.com");
        contato.setEndereco(end);
        contato.setTelefones(telefones);

        System.out.println(contato.toString());

        JAXBContext context = null;
        try {
            context = JAXBContext.newInstance("br.com.tutorial.jaxb");
            Marshaller marshaller = context.createMarshaller();
            JAXBElement element =
                    new ObjectFactory().createContato(contato);
            marshaller.setProperty(
                    javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT,
                    Boolean.TRUE
            );
            marshaller.marshal(element, System.out);
        } catch (JAXBException e) {
            e.printStackTrace();
        }

        try {
            JAXBContext.newInstance("br.com.tutorial.jaxb");
            Unmarshaller unmarshaller = context.createUnmarshaller();
            JAXBElement element =
                    (JAXBElement) unmarshaller.unmarshal(
                            new File("C:\\TutorialArquivos\\contato-jaxb.xml")
                    );
            Contato c1 = element.getValue();
            System.out.println(c1.toString());
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }
}

Saiba mais

Ballem

Marcio Ballem é bacharel em Sistemas de Informação pelo Centro Universitário Franciscano em Santa Maria/RS. Tem experiência com desenvolvimento Delphi e Java em projetos para gestão pública e acadêmica. Possui certificação em Java, OCJP 6.

Você pode gostar...