1.Criar uma classe simples serializável
Implemente Serializable e serialize/deserialize um objeto usando ObjectOutputStream e ObjectInputStream.
2. Testar a compatibilidade de versão
Serializar um objeto de uma versão antiga da classe e tentar desserializá-lo após uma mudança na estrutura da classe.
Testar o comportamento do serialVersionUID.
3. Criar uma forma serializada personalizada
Usar writeObject e readObject para definir manualmente a serialização de uma classe.
4. Explorar riscos de segurança
Criar um objeto vulnerável a ataques via serialização (exemplo: permitir modificação de campos privados via desserialização).
Corrigir a vulnerabilidade implementando readObject.
5. Comparar serialização com outras abordagens
Implementar persistência de um objeto com JSON (Gson, Jackson) e comparar com a serialização Java.
1. Faça uma classe serializável e demonstre a serialização e desserialização de um objeto dessa classe.
import java.io.*;
// Classe serializável
class Pessoa implements Serializable {
private static final long serialVersionUID = 1L; // UID para manter compatibilidade
private String nome;
private int idade;
public Pessoa(String nome, int idade) {
this.nome = nome;
this.idade = idade;
}
@Override
public String toString() {
return "Pessoa{nome='" + nome + "', idade=" + idade + "}";
}
}
public class SerializacaoExemplo {
public static void main(String[] args) {
Pessoa pessoa = new Pessoa("João", 30);
// Serializar objeto
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("pessoa.ser"))) {
out.writeObject(pessoa);
System.out.println("Objeto serializado com sucesso!");
} catch (IOException e) {
e.printStackTrace();
}
// Desserializar objeto
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("pessoa.ser"))) {
Pessoa pessoaLida = (Pessoa) in.readObject();
System.out.println("Objeto desserializado: " + pessoaLida);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
📌 Explicação: Esse código cria uma classe Pessoa serializável, salva um objeto em um arquivo e depois o lê de volta.
2. Modifique a classe do exercício anterior para incluir um novo campo e demonstre o problema de compatibilidade ao desserializar um objeto antigo.
import java.io.*;
class Pessoa implements Serializable {
private static final long serialVersionUID = 1L; // UID fixo para manter compatibilidade
private String nome;
private int idade;
private String email; // Novo campo adicionado
public Pessoa(String nome, int idade, String email) {
this.nome = nome;
this.idade = idade;
this.email = email;
}
@Override
public String toString() {
return "Pessoa{nome='" + nome + "', idade=" + idade + ", email='" + email + "'}";
}
}
public class SerializacaoProblema {
public static void main(String[] args) {
// Tentativa de desserializar um objeto criado antes da adição do campo 'email'
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("pessoa.ser"))) {
Pessoa pessoaLida = (Pessoa) in.readObject();
System.out.println("Objeto desserializado: " + pessoaLida);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
📌 Explicação: Se um objeto da versão anterior (sem email) for desserializado, pode causar InvalidClassException, pois o serialVersionUID e a estrutura da classe mudaram.
3. Corrija o problema do exercício anterior implementando uma forma serializada compatível usando serialVersionUID.
import java.io.*;
class Pessoa implements Serializable {
private static final long serialVersionUID = 1L;
private String nome;
private int idade;
private transient String email; // Campo transient não será serializado
public Pessoa(String nome, int idade, String email) {
this.nome = nome;
this.idade = idade;
this.email = email;
}
@Override
public String toString() {
return "Pessoa{nome='" + nome + "', idade=" + idade + "', email='" + (email != null ? email : "N/A") + "'}";
}
}
public class SerializacaoCorrigida {
public static void main(String[] args) {
// Desserializar objeto antigo sem email
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("pessoa.ser"))) {
Pessoa pessoaLida = (Pessoa) in.readObject();
System.out.println("Objeto desserializado: " + pessoaLida);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
📌 Explicação: O campo email foi marcado como transient, o que impede sua serialização e mantém a compatibilidade.
4. Demonstre como a serialização pode ser uma falha de segurança permitindo a modificação de um objeto privado.
import java.io.*;
class Usuario implements Serializable {
private static final long serialVersionUID = 1L;
private String nome;
private String senha; // Senha não protegida!
public Usuario(String nome, String senha) {
this.nome = nome;
this.senha = senha;
}
@Override
public String toString() {
return "Usuario{nome='" + nome + "', senha='" + senha + "'}";
}
}
public class FalhaSeguranca {
public static void main(String[] args) {
Usuario usuario = new Usuario("admin", "1234");
// Serializar usuário
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("usuario.ser"))) {
out.writeObject(usuario);
} catch (IOException e) {
e.printStackTrace();
}
// Modificar o arquivo manualmente e desserializar (Simulando um ataque)
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("usuario.ser"))) {
Usuario usuarioHackeado = (Usuario) in.readObject();
System.out.println("Usuário comprometido: " + usuarioHackeado);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
📌 Explicação: A senha é armazenada em texto plano e pode ser extraída ou modificada por um invasor ao alterar o arquivo serializado.
5. Corrija o problema de segurança do exercício anterior tornando os campos sensíveis transient e implementando writeObject() e readObject().
import java.io.*;
class UsuarioSeguro implements Serializable {
private static final long serialVersionUID = 1L;
private String nome;
private transient String senha; // Agora a senha não será serializada
public UsuarioSeguro(String nome, String senha) {
this.nome = nome;
this.senha = senha;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(encrypt(senha)); // Salva senha criptografada
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
senha = decrypt((String) in.readObject()); // Recupera senha descriptografada
}
private String encrypt(String senha) {
return senha == null ? null : new StringBuilder(senha).reverse().toString(); // Simples inversão
}
private String decrypt(String senhaCriptografada) {
return senhaCriptografada == null ? null : new StringBuilder(senhaCriptografada).reverse().toString();
}
@Override
public String toString() {
return "UsuarioSeguro{nome='" + nome + "', senha='" + senha + "'}";
}
}
public class SegurancaCorrigida {
public static void main(String[] args) {
UsuarioSeguro usuario = new UsuarioSeguro("admin", "1234");
// Serializar
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("usuarioSeguro.ser"))) {
out.writeObject(usuario);
} catch (IOException e) {
e.printStackTrace();
}
// Desserializar
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("usuarioSeguro.ser"))) {
UsuarioSeguro usuarioLido = (UsuarioSeguro) in.readObject();
System.out.println("Usuário seguro: " + usuarioLido);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
📌 Explicação: O campo senha agora é transient, e os métodos writeObject() e readObject() aplicam uma criptografia simples.
5. Comparar serialização com outras abordagens
5.1 Persistência usando Serialização Java
import java.io.*;
// Classe serializável
class Pessoa implements Serializable {
private static final long serialVersionUID = 1L;
private String nome;
private int idade;
public Pessoa(String nome, int idade) {
this.nome = nome;
this.idade = idade;
}
@Override
public String toString() {
return "Pessoa{nome='" + nome + "', idade=" + idade + "}";
}
}
public class SerializacaoJava {
public static void main(String[] args) {
Pessoa pessoa = new Pessoa("João", 30);
// Serializar em arquivo
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("pessoa.ser"))) {
out.writeObject(pessoa);
System.out.println("Objeto serializado: " + pessoa);
} catch (IOException e) {
e.printStackTrace();
}
// Desserializar do arquivo
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("pessoa.ser"))) {
Pessoa pessoaLida = (Pessoa) in.readObject();
System.out.println("Objeto desserializado: " + pessoaLida);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
📌 Pontos positivos: Integrado ao Java, eficiente em binário.
📌 Pontos negativos: Problemas de compatibilidade ao mudar a classe, não legível para humanos.
5.2 Persistência usando JSON com Gson
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.*;
// Classe normal sem Serializable
class PessoaGson {
private String nome;
private int idade;
public PessoaGson(String nome, int idade) {
this.nome = nome;
this.idade = idade;
}
@Override
public String toString() {
return "PessoaGson{nome='" + nome + "', idade=" + idade + "}";
}
}
public class PersistenciaGson {
public static void main(String[] args) {
PessoaGson pessoa = new PessoaGson("João", 30);
Gson gson = new GsonBuilder().setPrettyPrinting().create();
// Serializar em JSON
try (FileWriter writer = new FileWriter("pessoa.json")) {
gson.toJson(pessoa, writer);
System.out.println("Objeto serializado em JSON:\n" + gson.toJson(pessoa));
} catch (IOException e) {
e.printStackTrace();
}
// Desserializar do JSON
try (Reader reader = new FileReader("pessoa.json")) {
PessoaGson pessoaLida = gson.fromJson(reader, PessoaGson.class);
System.out.println("Objeto desserializado: " + pessoaLida);
} catch (IOException e) {
e.printStackTrace();
}
}
}
📌 Pontos positivos: Legível, fácil de integrar com APIs e persistência de dados.
📌 Pontos negativos: JSON ocupa mais espaço e é um pouco mais lento que binário.
5.3 Persistência usando JSON com Jackson
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.*;
// Classe normal sem Serializable
class PessoaJackson {
private String nome;
private int idade;
public PessoaJackson() {} // Jackson precisa de um construtor vazio
public PessoaJackson(String nome, int idade) {
this.nome = nome;
this.idade = idade;
}
@Override
public String toString() {
return "PessoaJackson{nome='" + nome + "', idade=" + idade + "}";
}
}
public class PersistenciaJackson {
public static void main(String[] args) {
PessoaJackson pessoa = new PessoaJackson("João", 30);
ObjectMapper objectMapper = new ObjectMapper();
// Serializar em JSON
try {
objectMapper.writeValue(new File("pessoa_jackson.json"), pessoa);
System.out.println("Objeto serializado em JSON com Jackson");
} catch (IOException e) {
e.printStackTrace();
}
// Desserializar do JSON
try {
PessoaJackson pessoaLida = objectMapper.readValue(new File("pessoa_jackson.json"), PessoaJackson.class);
System.out.println("Objeto desserializado: " + pessoaLida);
} catch (IOException e) {
e.printStackTrace();
}
}
}
📌 Pontos positivos: Alto desempenho, flexível, suporta diversas configurações.
📌 Pontos negativos: Requer bibliotecas externas, pode exigir configurações extras.
5.4 Comparação das abordagens
📌 Conclusão:
- A serialização Java é eficiente, mas tem problemas de compatibilidade e não é legível.
- O Gson é simples, fácil de usar e ideal para APIs.
- O Jackson tem melhor desempenho e é mais configurável, sendo ótimo para grandes aplicações.
Top comments (0)