Trabalhando com a biblioteca XStream em Java

Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Para efetuar o download você precisa estar logado. Clique aqui para efetuar o login
Confirmar voto
0
 (7)  (0)

Veja nesse artigo como trabalhar com a XStream, explorando recursos básicos e avançados dessa biblioteca.

Biblioteca XStream

Figura 1: Biblioteca XStream

O artigo possui as seguintes seções:

  • Introdução
  • Serializando/Deserializando Objeto Simples (XML)
  • Serializando/Deserializando Objeto Composto (XML)
  • Trabalhando com JSON
  • Ignorando novo campo no XML: método unmarshall
  • Ignorando gravação de campo no XML: método marshall
  • Conversores Personalizados
  • Usando Anotação

1) Introdução

A biblioteca XStream foi criada visando facilitar a manipulação de XML em Java, através da serialização/deserialização de objetos usando representação em XML. Veremos nesse artigo como trabalhar com essa biblioteca.

Nota: Utilizaremos nos exemplos desse artigo a versão 1.4.4, que pode ser obtida em: http://xstream.codehaus.org/download.html.

2) Serializando/Deserializando Objeto Simples

Listagem 1: Classe Livro

package br.com.devmedia.entity;

public class Livro {
    
    private int ano;
    private String titulo;
    private String isbn;

    public Livro() {
    }

    public Livro(int ano, String titulo, String isbn) {
        this.ano = ano;
        this.titulo = titulo;
        this.isbn = isbn;
    }

    public int getAno() {
        return ano;
    }

    public void setAno(int ano) {
        this.ano = ano;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }
    
    public String getTitulo() {
        return titulo;
    }

    public void setTitulo(String titulo) {
        this.titulo = titulo;
    }
}

Listagem 2: Serializando objeto

package br.com.devmedia.app;

import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.XStream;

public class SerializacaoXML {
    
    public static void main(String[] args) {
        XStream xstream = new XStream();
        Livro livro = new Livro(2000, "Assim Falou Zaratustra", "123");
        String xml = xstream.toXML(livro);
        System.out.println("XML = ");
        System.out.println(xml);
    }
}

Saída:

XML =
<br.com.devmedia.entity.Livro>
<ano>2000</ano>
<titulo>Assim Falou Zaratustra</titulo>
<isbn>123</isbn>
</br.com.devmedia.entity.Livro>

Nota: É necessário adicionar as dependências xstream-1.4.4.jar, xmlpull-1.1.3.1.jar e xpp3_min-1.1.4c.jar no seu projeto. Esses jars estão dentro do zip: https://nexus.codehaus.org/content/repositories/releases/com/thoughtworks/xstream/xstream-distribution/1.4.4/xstream-distribution-1.4.4-bin.zip.

É relativamente simples gerar a representação XML de um objeto Java usando XStream. Note que o nó raiz do XML é <br.com.devmedia.entity.Livro>, ou seja, o nome totalmente qualificado da classe. Para gerar um nome mais amigável, usaremos o método alias do XStream: xstream.alias("livro", Livro.class);

E assim a nova saída será:

<livro>
<ano>2000</ano>
<titulo>Assim Falou Zaratustra</titulo>
<isbn>123</isbn>
</livro>

Para converter o XML novamente num objeto, basta usar o método fromXML:

Listagem 3: Serializando/Deserializando objeto

package br.com.devmedia.app;

import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.XStream;

public class SerializaDeserializaXML {
    
    public static void main(String[] args) {        
        XStream xstream = new XStream();
        Livro livro = new Livro(2000, "Assim Falou Zaratustra", "123");
        // Serializando objeto
        xstream.alias("livro", Livro.class);
        String xml = xstream.toXML(livro);
        
        // Deserializando objeto
        Livro novoLivro = (Livro)xstream.fromXML(xml);

        System.out.println("Titulo = " + novoLivro.getTitulo());
        System.out.println("Ano = " + novoLivro.getAno());
        System.out.println("ISBN = " + novoLivro.getIsbn());
    }
}

Saída:

Titulo = Assim Falou Zaratustra
Ano = 2000
ISBN = 123

3) Serializando/Deserializando Objeto Composto

Listagem 4: Classe Livro2 com atributo Editora

package br.com.devmedia.entity;

public class Livro2 {
    
    private int ano;
    private String titulo;
    private String isbn;
    private Editora editora;

    public Livro2() {
    }

    public Livro2(int ano, String titulo, String isbn, Editora editora) {
        this.ano = ano;
        this.titulo = titulo;
        this.isbn = isbn;
        this.editora = editora;
    }

    public Editora getEditora() {
        return editora;
    }

    public void setEditora(Editora editora) {
        this.editora = editora;
    }

    public int getAno() {
        return ano;
    }

    public void setAno(int ano) {
        this.ano = ano;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }
    
    public String getTitulo() {
        return titulo;
    }

    public void setTitulo(String titulo) {
        this.titulo = titulo;
    }
}

Listagem 5: Classe Editora com atributo Endereco

package br.com.devmedia.entity;

public class Editora {
    
    private String nome;
    private Endereco endereco;

    public Editora() {
    }

    public Editora(String nome, Endereco endereco) {
        this.nome = nome;
        this.endereco = endereco;
    }

    public Endereco getEndereco() {
        return endereco;
    }

    public void setEndereco(Endereco endereco) {
        this.endereco = endereco;
    }

    public String getNome() {
        return nome;
    }

    public void setNome(String nome) {
        this.nome = nome;
    }
}

Listagem 6: Classe Endereco

package br.com.devmedia.entity;

public class Endereco {
   
    private int numero;
    private String logradouro;
    private String CEP;

    public Endereco() {
    }

    public Endereco(int numero, String logradouro, String CEP) {
        this.numero = numero;
        this.logradouro = logradouro;
        this.CEP = CEP;
    }

    public String getCEP() {
        return CEP;
    }

    public void setCEP(String CEP) {
        this.CEP = CEP;
    }

    public String getLogradouro() {
        return logradouro;
    }

    public void setLogradouro(String logradouro) {
        this.logradouro = logradouro;
    }

    public int getNumero() {
        return numero;
    }

    public void setNumero(int numero) {
        this.numero = numero;
    }
}   

Listagem 7: Serializando/Deserializando Objeto Livro2

package br.com.devmedia.app;

import br.com.devmedia.entity.Editora;
import br.com.devmedia.entity.Endereco;
import br.com.devmedia.entity.Livro2;
import com.thoughtworks.xstream.XStream;

public class SerializaDeserializaObjetoComposto {
    
    public static void main(String[] args) {
        XStream xstream = new XStream();
        Endereco endereco = new Endereco(100, "Wall Street", "XXXXXX-XXX");
        Editora editora = new Editora("Abril", endereco);        
        Livro2 livro = new Livro2(2000, "Assim Falou Zaratustra", "123", editora);
        xstream.alias("livro", Livro2.class);
        xstream.alias("editora", Editora.class);
        xstream.alias("endereco", Endereco.class);
        String xml = xstream.toXML(livro);
        System.out.println("XML = ");
        System.out.println(xml);        
        
        Livro2 novoLivro = (Livro2)xstream.fromXML(xml);
        System.out.println("Titulo = " + novoLivro.getTitulo());
        System.out.println("Ano = " + novoLivro.getAno());
        System.out.println("ISBN = " + novoLivro.getIsbn());
        System.out.println("Editora = " + novoLivro.getEditora().getNome());
        System.out.println("Endereco = " + novoLivro.getEditora().getEndereco().getCEP());
        System.out.println("Logradouro = " + novoLivro.getEditora().getEndereco().getLogradouro());
    }
}   

Saída:

XML =
<livro>
; <ano>2000</ano>
<titulo>Assim Falou Zaratustra</titulo>
<isbn>123</isbn>
<editora>
<nome>Abril</nome>
<endereco>
<numero>100</numero>
<logradouro>Wall Street</logradouro>
<CEP>XXXXXX-XXX</CEP>
</endereco>
</editora>
</livro>

Titulo = Assim Falou Zaratustra
Ano = 2000
ISBN = 123
Editora = Abril
Endereco = XXXXXX-XXX
Logradouro = Wall Street

A serialização/deserialização de objetos complexos é praticamente idêntica a de objetos simples.

4) Trabalhando com JSON

Ao invés de XML, podemos também usar JSON para a representação de objetos:

Nota: A dependência jettison-1.2.jar deve ser adicionada ao projeto.

Listagem 8: Usando JSON com Objeto Simples

package br.com.devmedia.app;

import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;

public class SerializaDeserializaJSON {
    
    public static void main(String[] args) {
        
        XStream xstream = new XStream(new JettisonMappedXmlDriver());
        Livro livro = new Livro(2000, "Assim Falou Zaratustra", "123");
        xstream.alias("livro", Livro.class);
        String json = xstream.toXML(livro);
        System.out.println("JSON = ");
        System.out.println(json);
        
        Livro novoLivro = (Livro)xstream.fromXML(json);
        System.out.println("Titulo = " + novoLivro.getTitulo());
        System.out.println("Ano = " + novoLivro.getAno());
        System.out.println("ISBN = " + novoLivro.getIsbn());
    }
}   

Saída:

JSON =
{"livro":{"ano":2000,"titulo":"Assim Falou Zaratustra","isbn":123}}

Titulo = Assim Falou Zaratustra
Ano = 2000
ISBN = 123

Para trabalhar com JSON, basta passar para o construtor da classe XStream o driver JettisonMappedXmlDriver(). Se nenhum driver for definido, o default será XppDriver(). Podemos definir outros drivers, como DomDriver, StaxDriver, etc. Todos os drivers são subclasses (direta ou indiretamente) da classe AbstractDriver, que implementa a interface HierarchicalStreamDriver.

A versão JSON da classe Livro2 seria:

Listagem 9: JSON de Livro2

{"livro":{"ano":2000,"titulo":"Assim Falou Zaratustra","isbn":123,"editora":  {"nome":"Abril","endereco":{"numero":100,"logradouro":"Wall Street","CEP":"XXXXXX-XXX"}}}}

5) Ignorando novo campo no XML: método unmarshall

Imagine que tenhamos gravado a representação XML de um objeto Livro num arquivo .xml:

<livro>
<ano>2000</ano>
<titulo>Assim Falou Zaratustra</titulo>
<isbn>123</isbn>
</livro>

E que por algum motivo, adicionamos um novo atributo na classe Livro, no caso, o campo valor (double). O que acontece se tentarmos deserializar o XML, transformando-o num objeto?

Nesse caso, a deserialização irá funcionar e o campo valor do objeto livro terá valor 0.0.

Agora, e se adicionarmos uma tag <valor> no XML e não adicionarmos um campo valor para a classe Livro?

Listagem 10: DeserializacaoNovoCampo

package br.com.devmedia.app;

import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.XStream;

public class DeserializacaoNovoCampo {
    
    public static void main(String[] args) {
        
        StringBuilder xml = new StringBuilder();
        xml.append("<livro><ano>2000</ano>");
        xml.append("<titulo>Assim Falou Zaratustra</titulo>");
        xml.append("<isbn>123</isbn>");
        xml.append("<valor>10.0</valor>");
        xml.append("</livro>");
        
        XStream xstream = new XStream();
        xstream.alias("livro", Livro.class);
        
        Livro novoLivro = (Livro)xstream.fromXML(xml.toString());
        System.out.println("Titulo = " + novoLivro.getTitulo());
        System.out.println("Ano = " + novoLivro.getAno());
        System.out.println("ISBN = " + novoLivro.getIsbn());
    }
}

No caso, será lançada a seguinte exceção:

Exception in thread "main" com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter$UnknownFieldException: No such field br.com.devmedia.entity.Livro.valor.

Como ignorar o novo campo no XML sem alterar a classe Livro? Para isso devemos usar o conceito de conversor do XStream:

Listagem 11: Conversor Personalizado

package br.com.devmedia.converter;

import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class LivroConverter implements Converter {

    @Override
    public boolean canConvert(Class clazz) {
        return clazz.equals(Livro.class);
    }

    @Override
    public void marshal(Object value, HierarchicalStreamWriter writer,
            MarshallingContext context) {
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader reader,
            UnmarshallingContext context) {

        Livro livro = new Livro();
        while (reader.hasMoreChildren()) {
            reader.moveDown();
            if ("ano".equals(reader.getNodeName())) {
                Integer ano = (Integer)context.convertAnother(livro, Integer.class);
                livro.setAno(ano);
            } else if ("titulo".equals(reader.getNodeName())) {
                String titulo = (String) context.convertAnother(livro, String.class);
                livro.setTitulo(titulo);
            } else if ("isbn".equals(reader.getNodeName())) {
                String isbn = (String) context.convertAnother(livro, String.class);
                livro.setIsbn(isbn);
            }            
            reader.moveUp();
        }
        
        return livro;
    }
}

Um conversor personalizado para a classe Livro foi criado. Ele irá processar todos os atributos do XML, menos a tag <valor>.

Para criar um conversor, basta criar uma classe que implementa a interface Converter e implementar seus três métodos:

  • public boolean canConvert(Class clazz): Indica as classes que podemos manipular.
  • public void marshal: Faz a conversão do objeto em XML.
  • public Object unmarshal: Faz a conversão do XML em objeto.

Para o método canConvert, estamos indicando que apenas instâncias da classe Livro serão manipuladas (ou seja, excluindo até mesmos possíveis subclasses de Livro).

No exemplo, temos um XML alterado (tag <valor> adicionada). Queremos transformar esse XML em um objeto Livro, ignorando essa nova tag. Por isso, dentro do while, processamos todas as tag, ignorando apenas a tag <valor>.

Uma vez criado o conversor personalizado, devemos registrá-lo através do método registerConverter da classe XStream:

Listagem 12: Convertendo XML alterado

package br.com.devmedia.app;

import br.com.devmedia.converter.LivroConverter;
import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.XStream;

public class DeserializacaoNovoCampo2 {
    
    public static void main(String[] args) {
        
        StringBuilder xml = new StringBuilder();
        xml.append("<livro><ano>2000</ano>");
        xml.append("<titulo>Assim Falou Zaratustra</titulo>");
        xml.append("<isbn>123</isbn>");
        xml.append("<valor>10.0</valor>");
        xml.append("</livro>");
        
        XStream xstream = new XStream();
        xstream.alias("livro", Livro.class);

        // Registrando conversor personalizado
        xstream.registerConverter(new LivroConverter());
        
        Livro novoLivro = (Livro)xstream.fromXML(xml.toString());
        System.out.println("Titulo = " + novoLivro.getTitulo());
        System.out.println("Ano = " + novoLivro.getAno());
        System.out.println("ISBN = " + novoLivro.getIsbn());
    }
}

Saída:

Titulo = Assim Falou Zaratustra
Ano = 2000
ISBN = 123

Nessa nova versão, usando o LivroConverter, a tag <valor> do XML será ignorada, e a exceção não será lançada.

6) Ignorando gravação de campo no XML: método marshall

Da mesma forma que podemos ignorar uma tag nova no XML, podemos também personalizar a geração do XML, ignorando a gravação de certos campos. Imagine que não seja necessário gravar o campo isbn no XML. Para executar essa tarefa, criaremos um conversor personalizado:

Listagem 13: LivroConverter2

package br.com.devmedia.converter;

import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class LivroConverter2 implements Converter {

    @Override
    public boolean canConvert(Class clazz) {
        return clazz.equals(Livro.class);
    }

    @Override
    public void marshal(Object value, HierarchicalStreamWriter writer,
            MarshallingContext context) {
        
        Livro livro = (Livro)value;
        writer.startNode("ano");
        context.convertAnother(livro.getAno());
        writer.endNode();            

        writer.startNode("titulo");
        context.convertAnother(livro.getTitulo());
        writer.endNode();        
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader reader,
            UnmarshallingContext context) {
        return null;
    }
}

Nesse conversor, estamos atuando sobre o método marshall, que transforma o objeto em XML. Gravamos os 2 campos: ano e titulo, e ignoramos o campo isbn.

Listagem 14: Ignorando campo na geração do XML

package br.com.devmedia.app;

import br.com.devmedia.converter.LivroConverter2;
import br.com.devmedia.entity.Livro;
import com.thoughtworks.xstream.XStream;

public class SerializacaoPersonalizadaXML {
    
    public static void main(String[] args) {
        
        Livro livro = new Livro(2000, "Assim Falou Zaratustra", "123");

        XStream xstream = new XStream();
        
        // Registrando novo conversor
        xstream.registerConverter(new LivroConverter2());        
        // Registrando alias
        xstream.alias("livro", Livro.class);
        // Gerando XML
        String xml = xstream.toXML(livro);
        
        System.out.println("XML = ");
        System.out.println(xml);
    }
}

Saída:

XML =
<livro>
<ano>2000</ano>
<titulo>Assim Falou Zaratustra</titulo>
</livro>

Notem que a tag <isbn> não foi gravada no XML.

7) Conversores Personalizados

Nos dois exemplos acima, ficou claro o potencial de uso de um conversor personalizado. Para criar um conversor, basta ter uma classe que implementa a interface Converter e codificar seus 3 métodos.

O XStream possui vários tipos de conversores pré-definidos. Para ver a lista completa, acesse: http://xstream.codehaus.org/converters.html.

Para mais detalhes sobre como criar conversores personalizados: http://xstream.codehaus.org/converter-tutorial.html.

8) Usando Anotação

Listagem 14: Classe Livro4

package br.com.devmedia.entity;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;

@XStreamAlias("livro")
public class Livro4 {
    
    private int ano;
    private String titulo;
    private String isbn;
    
    @XStreamOmitField
    private double valor;

    public Livro4() {
    }

    public Livro4(int ano, String titulo, String isbn, double valor) {
        this.ano = ano;
        this.titulo = titulo;
        this.isbn = isbn;
        this.valor = valor;
    }

    public int getAno() {
        return ano;
    }

    public void setAno(int ano) {
        this.ano = ano;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }

    public String getTitulo() {
        return titulo;
    }

    public void setTitulo(String titulo) {
        this.titulo = titulo;
    }

    public double getValor() {
        return valor;
    }

    public void setValor(double valor) {
        this.valor = valor;
    }    
}    

Na classe, usamos a anotação @XstreamAlias("livro") para definir o alias. E utilizamos a anotação @XstreamOmitField para ignorar a gravação do campo valor no XML. Ao usar anotações, devemos informar esse fato ao XStream, através do método processAnnotations:

Listagem 15: Usando anotações

package br.com.devmedia.app;

import br.com.devmedia.entity.Livro4;
import com.thoughtworks.xstream.XStream;

public class SerializacaoAnotacaoXML {
    
    public static void main(String[] args) {
        
        XStream xstream = new XStream();
        xstream.processAnnotations(Livro4.class);
        Livro4 livro = new Livro4(2000, "Assim Falou Zaratustra", "123", 10.0);
        String xml = xstream.toXML(livro);
        System.out.println("XML = ");
        System.out.println(xml);
    }
}    

Saída:

XML =
<livro>
<ano>2000</ano>
<titulo>Assim Falou Zaratustra</titulo>
<isbn>123</isbn>
</livro>

O campo valor foi ignorado na hora de gerar o XML.

Conclusão

A biblioteca XStream torna fácil a manipulação de XML dentro de aplicações Java. O ganho de produtividade é alto, portanto, sempre que possível, use o XStream para manipular XML (e até JSON) dentro de suas aplicações.

Para além do básico, temos os conversores personalizados, que ajudam a tratar representações mais complexas de tags ou valores dentro do XML.

Obrigado e até a próxima!

Nota: No artigo Conhecendo o Apache Mina Framework, foi utilizado XStream para trafegar objetos de um cliente TCP/IP para um servidor, ou seja, criou-se um mecanismo próprio de serialização de objetos baseado em TCP e XML. O cliente converte um objeto POJO em XML, o envia pela rede, e o servidor transforma o XML em um objeto POJO novamente.

Referências:

 
Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Receba nossas novidades
Ficou com alguma dúvida?