fev

8

Reflexão Computacional – Exemplo prático

By Felipe Saab

E ae galera,
Primeiro de tudo, obrigado pelas visitas… estava vendo as estatísticas do site e todo dia tem alguém dando uma olhada no blog. Obrigado mesmo. :D

No último post eu mostrei uma técnica bem interessante da programação dinâmica: a reflexão, através da qual agente consegue perguntar pra um objeto qualquer quais são suas variáveis de instância (inclusive os seus valores) e seus métodos.

Beleza, daí um monte de gente me perguntou: “Pra que diabos eu preciso saber disso?” ou então “Quando eu vou usar?” entre outras… Para exemplificar o uso da reflexão vamos criar uma pequena aplicação. Nada muito complicado… uma agenda de contatos. OK, só mais um detalhe: não vou explicar nada sobre a conexão com o banco de dados, ou tratamento de erros ou outras coisas que vou utilizar no tutorial, o foco vai ser a reflexão. Qualquer dúvida é só deixar um comentário que a gente resolve.

Então vamos ao que interessa! Primeiro vamos criar uma tabela no banco de dados para representar um contato:

CREATE TABLE `contatos` (
 
`con_id` INT NOT NULL ,
`con_nome` VARCHAR( 60 ) NOT NULL ,
`con_endereco` VARCHAR( 100 ) NOT NULL ,
`con_email` VARCHAR( 50 ) NOT NULL ,
`con_telefone` VARCHAR( 20 ) NOT NULL ,
PRIMARY KEY ( `con_id` )
 
) ENGINE = INNODB CHARACTER SET utf8 COLLATE utf8_unicode_ci;

Pronto, com o banco pronto vamos para a aplicação. Crie uma nova aplicação no NetBeans (aplicação para área de trabalho, porque ela já importa o .jar do swing), apague tudo que ele crie a seguinte estrutura:

image

Vamos aplicar um pequeno padrão aqui, algo que lembra o MVC, que é utilizar uma estrutura de 3 camadas:

  1. Apresentação: mostra os dados para o usuário;
  2. Negócios: cuida das regras de negócio da aplicação (nada muito complexo nessa nossa agenda);
  3. Persistência: cuida do armazenamento físico dos dados.

Não vou explicar muito a engenharia da coisa portanto vamos em frente. Vamos começar de baixo pra cima, ou seja, vamos ver primeiro a classe da camada de persistência:

public class ConexaoMySQL {
 
    private Connection connect;
    private String erro = "";
 
    public static ConexaoMySQL getConexao() throws Exception {
        ConexaoMySQL c = new ConexaoMySQL();
        Connection connect = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://localhost/agenda";
            connect = DriverManager.getConnection(url, "root", "123");
            c.setConnection(connect);
        } catch (ClassNotFoundException cnfex) {
            throw new Exception("Falha ao ler o driver JDBC" + cnfex.toString());
        } catch (SQLException sqlex) {
            throw new Exception("Impossível conectar com a base de dados" + sqlex.toString());
        } catch (Exception ex) {
            throw new Exception("Outro erro" + ex.toString());
        }
        return c;
    }
 
    public void close() {
        try {
            this.connect.close();
        } catch (Exception ex) {
            System.out.println(ex.toString());
        }
    }
 
    private void setErro(String erro) {
        this.erro = erro;
    }
 
    private void setConnection(Connection connection) {
        this.connect = connection;
    }
 
    public Connection getConnection() {
        return this.connect;
    }
 
    public String getMensagemErro() {
        return erro;
    }
 
    public boolean getEstadoConexao() {
        if (connect == null) {
            return false;
        } else {
            return true;
        }
    }
 
    public boolean manipular(String sql) // inserir, alterar,excluir
    {
        try {
            Statement statement = connect.createStatement();
            int result = statement.executeUpdate(sql);
            statement.close();
            if (result != 1) {
                return true;
            }
        } catch (SQLException sqlex) {
            erro = "Erro: " + sqlex.toString();
            return false;
        }
        return false;
    }
 
    public int getMaxPK(String tabela, String chave) {
        String sql = "select max(" + chave + ") as max from " + tabela;
        int max = 0;
        ResultSet rs = null;
        try {
            Statement statement = connect.createStatement(
            ResultSet.TYPE_SCROLL_INSENSITIVE,
            ResultSet.CONCUR_READ_ONLY);
            System.out.println(sql);
            rs = statement.executeQuery(sql);
 
            if (rs.next()) {
                max = rs.getInt("max");
            }
            statement.close();
        } catch (SQLException sqlex) {
            erro = "Erro: " + sqlex.toString();
            return -1;
        }
 
        return max;
    }
 
    //aqui que a mágica da reflexão acontece
    public void Insere(String tabela, Object objeto) throws Exception {
        SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd");
        String sql = "INSERT INTO " + tabela.toUpperCase() + "(";
 
        //percorre os atributos vendo o nome dos campos
        Class classe = objeto.getClass();
        for (Field f : classe.getDeclaredFields()) {
            f.setAccessible(true);
            sql += f.getName() + ",";
        }
 
        sql = sql.substring(0, sql.length() - 1); //retira a ultima virgula..
        sql += ") VALUES(";
 
        //percorre os atributos vendo o valor dos campos
        for (Field f : classe.getDeclaredFields()) {
            f.setAccessible(true);
            if (f.getType().getSimpleName().equals("String")) {
                sql += "'" + f.get(objeto) + "',";
            } else {
                sql += f.get(objeto) + ",";
            }
        }
 
        sql = sql.substring(0, sql.length() - 1); //retira a ultima virgula..
        sql += ")";
 
        //executa a query
        System.out.println(sql);
        if (!manipular(sql)) {
            throw new Exception(getMensagemErro());
        }
    }
}

O único método que merece explicações é o método Insere(String tabela, Object objeto) que utiliza da reflexão computacional para criar dinâmicamente o SQL referente à inserção do objeto “objeto” na tabela “tabela”.

O código é bem simples de entender:

  1. Inicializa a variável responsável por guardar o SQL;
  2. Passa por todos os atributos do objeto adiciondo os nomes deles ao SQL;
  3. Passa pelos atributos novamente, só que desta vez adicionando os valores dos atributos (caso seja uma variável da classe String, adiciona aspas simples antes e depois do valor).
  4. Executa a query. Se algo deu errado, joga uma nova exceção com a mensagem de erro.

Muito simples não?!?!?
Vamos ver como funciona na prática então, vamos subir uma camada e ver como funciona a classe Contatos.java:

public class Contatos {
 
    private int con_id;
    private String con_nome;
    private String con_endereco;
    private String con_telefone;
    private String con_email;
 
    public Contatos(String con_nome, String con_endereco, String con_telefone, String con_email) throws Exception {
        ConexaoMySQL c = ConexaoMySQL.getConexao();
        this.con_id = c.getMaxPK("contatos", "con_id") + 1;
        c.close();
        this.con_nome = con_nome;
        this.con_endereco = con_endereco;
        this.con_telefone = con_telefone;
        this.con_email = con_email;
    }
 
    public void Insere() throws Exception{
        if (this.con_nome.equals("") || this.con_telefone.equals("")){
            throw new Exception("Preencha pelo menos o nome e o telefone.");
        }
 
        ConexaoMySQL c = ConexaoMySQL.getConexao();
        c.Insere("contatos", this);
        c.close();
    }
 
//getters e setters
}

Uma classe extremamente simples que representa fielmente a tabela que criamos no banco de dados.

O construtor não exige o ID do contato, ao invés disso ele pega um novo para o contato (busca no banco o maior ID existente e soma 1).

O método Insere() não faz nada além de validar se pelo menos o nome e o telefone estão preenchidos e chamar o método Insere(..) da camada inferior passando uma String com o nome da tabela e passando ele mesmo (o objeto Contato) através da palavra reservada this. Caso ocorra algum erro na query, será retornada uma exceção do método Insere(…) da classe ConexaoMySQL.java para o método Insere() da classe Contato.java e este por sua vez irá retornar essa mesma exceção para quem o chamou.

Os nomes dos atributos devem ser iguais aos nomes das colunas da tabela, pois o método Insere(..) da classe ConexaoMySQL.java trata cada atributo como uma coluna da tabela para montar o SQL, como já vimos antes.

Agora vem a última (ou a primeira, depende do ponto de vista.. :P ) camada: a responsável pela apresentação. A classe Principal.java é um JFrame que vai fornecer a interface visual para o usuário. Deve ficar mais ou menos assim:

image

Os campos de texto são JTextFields com os respectivos nomes: jTF_Nome, jTF_Endereco, jTF_Telefone e jTF_Email.

Creio que eu nem precise comentar sobre o que o botão Limpar faz não é?

O código do botão Inserir segue abaixo:

public void Inserir() {
    try {
        Contatos c = new Contatos(jTF_Nome.getText(), jTF_Endereco.getText(), jTF_Telefone.getText(), jTF_Email.getText());
        c.Insere();
        JOptionPane.showMessageDialog(this, "Contato adicionado com sucesso");
    } catch (Exception e) {
        JOptionPane.showMessageDialog(this, e.getMessage());
    }
}

Cria-se um objeto Contato a partir dos dados do formulário e chama o método Insere(), caso ocorra algum erro, o programa desviará para o bloco catch(…) e será mostrado o erro, mas se der tudo certo, será mostrada a mensagem de sucesso.

DICA: Debuge o projeto para ver como o SQL é montado dinâmicamente. É bem interessante.

É isso! Repare como o código fica simples e fácil de ler.
Ao invés de ter uma classe só que cuida de mostrar os dados, validá-los, tratar a conexão com o banco e tratar o SQL (como eu mesmo já fiz e sei que muita gente faz), agora a gente tem uma estrutura muito bem projetada que facilita a manutenção e futuras mudanças, como por exemplo:

  1. Se você quiser guardar o twitter do contato também, basta criar uma nova coluna no banco, um novo atributo na classe Contatos.java e um novo campo de texto na classe de apresentação. Não precisa se preocupar com SQL pois a reflexão cuida disso pra nós.
  2. Se precisar mudar o banco para o SQL Server, basta trocar a classe ConexaoMySQL por uma ConexaoSQLServer. Não há necessidade de alteração na lógica nem na apresentação do projeto.

Minha intenção neste post era mostrar um exemplo somente sobre reflexão, mas eu acabei mostrando um pouco sobre o modelo de desenvolvimento em 3 camadas, algo realmente muito útil quando se trata do desenvolvimento de software.

Obrigado a você que teve paciência de ler até aqui.
Quem quiser o projeto do NetBeans, basta pegar aqui.
Qualquer dúvida é só deixar um comentário.

[] e até a próxima,
Saab.

PS: Só queria deixar uns créditos a outras pessoas:

Professor Silvio Carro (FIPP) que criou a base para a classe de conexão com o banco. A maior parte do código foi feito por ele.

Professor Emerson Silas Dória (FIPP) que mostrou esses conceitos de reflexão e camadas em aula, o que motivou a criação destes posts.

6 Responses so far

[...] This post was mentioned on Twitter by André Húngaro, Felipe Saab. Felipe Saab said: #JavaSimples Post novo pra quem não tinha entendido o anterior: Reflexão Computacional – Exemplo prático -> http://bit.ly/dnYZ9e [...]

Mto legal esse post sobre Java Reflections. Gostei mto de ver a utilidade disso! É uma pena que no meu curso de TADS naum vi 10% do que provavelmente você viu no seu curso… Mas nada que o Professor Google naum resolva!!! Vou criar um link pro seu site no meu blog!!! Tô fazendu q nem vc… tentando passar o meu conhecimento aos outros que querem aprender Java e tem dificuldade… Acho q vc pode contribuir mto com todos… Espero q continue assim!!!

Opa, obrigado Valmir!
Sempre que sobra um tempinho eu deixo uma dica nova pro pessoal aqui… :D
Caso você tenha interesse agente pode trocar os banners dos sites, é só me dar um toque.

[] e obrigado pelo comentário.

[...] exemplo prático de como funciona Java Reflection é dado no site javasimples.com.br, aliás, possui muitos outros artigos interessantes. Vale a pena [...]

boa tarde

Pesquisando sobre reflexão computacional, encontrei esse material, gostaria de saber se existe algum video sobre o assunto, pois estou montando um seminario para materia em Java e gostaria de apresentar em sala de aula, será que pode nos ajudar? Segue meu e-mail para contato.

obrigada desde já.

Rubia

Oi Rubia,
Desculpe mas eu nunca vi nenhum vídeo sobre o assunto. Nas minhas pesquisas eu utilizei mais a documentação do que outras fontes.
Mas nada a impede de montar um pequeno vídeo sobre isso… Caso você o faça, prometo que ponho aqui no site! :D

Boa sorte,
Saab.

Leave a comment