DEV Community

Weuller Brenneguer
Weuller Brenneguer

Posted on • Updated on

Como alavancar sua carreira JAVA conhecendo a injeção de dependência do Spring.

Estudando sobre Spring Framework e as tecnologias da ferramenta para facilitar o desenvolvimento JAVA, certamente vai se deparar com a injeção de dependência. Talvez, como eu, já tenha usado sem compreender muito, mas chegou a hora de desvendá-la.

Uma das facilidades é como container spring gerencia todo o ciclo de vida das dependências do projeto. Para entender isso, irei apresentá-lo ao conceito de inversão de dependência, depois vamos ver na prática, como usar isso para facilitar e ganhar agilidade no desenvolvimento ou manutenção do projeto JAVA.

1 - Container Spring.
2 - Configurando dependências manualmente.
3 - Conceito de injeção de dependência.
4 - Criando Bean gerenciável pelo Spring.
5 - Injetando dependência com o construtor.
6 - Injetando dependência com o método Setter.
7 - Injetando dependência no atributo.

Container Spring

Ao desenvolver o projeto com Spring Framework, as classes serão criadas, definimos tudo que será necessário para o seu funcionamento, o IoC Container, é o responsável por gerenciar essas dependências.
O IoC container, promove o que chamamos de inversão de dependência e usamos a Injeção de dependência
para obter essa inversão. Normalmente, ao criar objetos JAVA, fazemos a instanciação durante o uso, com o new Object();. Isso pode ser bem trabalhoso e repetitivo, veja o exemplo de uma classe controladora do DAO (Data Acess Object), que tem o objetivo de carregar dados de um cliente no banco de dados.

Configurando dependências manualmente.

Classe ClienteController

public class ClienteController {

   ClienteDAO clienteDao;

   public Cliente salvar(Cliente cliente) {
      clienteDao = new ClienteDAO();
      return clienteDao.add(cliente);
   }

   public List<Cliente> listarTodos() {
      clienteDao = new ClienteDAO();
      return clienteDao.list();
   }

   public Cliente alterar(Cliente cliente) {
      clienteDao = new ClienteDAO();
      return clienteDao.add(cliente);
   }

   public void excluir(Cliente cliente) {
      clienteDao = new ClienteDAO();
      clienteDao.remove(cliente);
   }

   public Cliente consultaPorId(Long id) {
      clienteDao = new ClienteDAO();
      return clienteDao.findById(id);
   }
}

Enter fullscreen mode Exit fullscreen mode

Observe que, tenho que instanciar manualmente o meu clienteDao, a classe ClienteController passa então, a ter uma dependência, sendo então necessário um ClienteDaO para que ela funcione.
Veja outro exemplo, agora na classe ClienteDAO, onde tenho que instanciar e abrir as conexões para o banco de dados:

Classe ClienteDAO

public class ClienteDAO implements DataAcessObject<Cliente> {

    private PreparedStatement query = null;
    private ResultSet rs = null;

    @Override
    public List<Cliente> list() {
        Connection conn = ConnectionFactory.abrirConexao(); /* instanciando o objeto */
        String sql = "Select * from clientes where not ind_deletado";
        List<Cliente> clientes = new ArrayList<Cliente>();
        Cliente cliente = null;

        try {
            query = conn.prepareStatement(sql);
            rs = query.executeQuery();
            while (rs.next()) {
                cliente = new Cliente(rs.getString("nome"), rs.getString("telefone"), rs.getString("email"));
                clientes.add(cliente);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            ConnectionFactory.fecharConexao(conn, query, rs);
        }
        return clientes;

    }

    @Override
    public List<Cliente> findByName(String name) {
        Connection conn = ConnectionFactory.abrirConexao();/* instanciando o objeto */
        String sql = "Select * from clientes where not ind_deletado and nome like ? ";
        List<Cliente> clientes = new ArrayList<Cliente>();
        Cliente cliente = null;

        try {
            query = conn.prepareStatement(sql);
            query.setString(1, "%" + name + "%");
            rs = query.executeQuery();
            while (rs.next()) {
                cliente = new Cliente(rs.getString("nome"), rs.getString("email"), rs.getString("telefone"));
                clientes.add(cliente);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            ConnectionFactory.fecharConexao(conn, query, rs);
        }

        return clientes;
    }
/*Outros métodos da interface...*/
}

Enter fullscreen mode Exit fullscreen mode

Da mesma forma que no objeto anterior, tenho que dizer quais são as dependências, aqui teremos que instanciar Connection, PreparedStatement, ResultSet.
Esta é a forma tradicional, onde declaramos as dependências de forma manual, em cada classe ou método, isso promove acoplamento, tornando complicada a manutenção. Um conceito de segurança de dados, é o de que, toda conexão aberta, deve ser fechada, também fazemos isso hard-code.
Na classe ClienteDaO, os métodos possuem try, catch e finally. No finally, é o momento em que a aplicação identifica que não preciso mais do objeto Connection conn; e ele pode ser descartado. Abrir conexões com o banco, consomem bastante processamento, o spring pode facilitar essa ação, abrindo uma conexão no inicio da aplicação, segurando ela durante todo o processo, esse é o padrão Singleton, então vamos usar o Spring para fazer essas configurações de forma mais inteligente e simplificada.

Conceito de injeção de dependência.

Injeção de Dependência, D.I ou Dependency Injection, remete a ideia de algo que vem de fora para dentro, o IoC tem a obrigação de instanciar, configurar e montar as dependências da nossa classe. Bean é como todo e qualquer objeto que será gerenciado pelo IoC Container é chamado. Classes de domínio, como a classe Cliente, não devem ser Beans, pois não quero criar vários objetos do tipo Cliente quando a aplicação é iniciada, faremos isso manualmente onde for necessário, é a forma correta, usamos injeção de dependência normalmente em classes de serviço ou DAO.

Como o Spring Funciona:
Como o spring funciona

Criando Bean gerenciável pelo Spring.

Agora que sabemos o conceito de injeção de dependência, e como ela funciona, vamos ver como facilitar isso usando a ferramenta.
Temos alguns pontos de Injeção no Spring framework, podemos configurá-la em arquivos xml, mas vamos fazer por meio das anotações, não porque são melhores que o xml, mas pelo contexto que é a busca por praticidade.
Notações são palavras reservadas do framework, metadados que usamos para marcar métodos, classes ou declaração, atributo. O IoC Container, só consegue gerenciar componentes Spring, por esse motivo marcamos as classes com @Component ou ainda com @Controller, @Repository, @Service, todos eles são @Component e podem ser gerenciados pelo spring.
Então vamos marcar a classe ClienteDAO com @Repository e vamos fazer um construtor na classe ClienteController, ele será o nosso primeiro ponto de injeção.

Classe ClienteDAO agora com @Repository

@Repository
public class ClienteDAO implements DataAcessObject<Cliente> {

    private PreparedStatement query = null;
    private ResultSet rs = null;

    public ClienteDAO() {
        System.out.println("EU CHAMEI O CLIENTE DAO AO INICIAR");
    }

    @Override
    public List<Cliente> list() {
        Connection conn = ConnectionFactory.abrirConexao(); /* instanciando o objeto */
        String sql = "Select * from clientes where not ind_deletado";
        List<Cliente> clientes = new ArrayList<Cliente>();
        Cliente c = null;

        try {
            query = conn.prepareStatement(sql);
            rs = query.executeQuery();
            while (rs.next()) {
                c = new Cliente(rs.getString("nome"), rs.getString("telefone"), rs.getString("email"));
                clientes.add(c);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            ConnectionFactory.fecharConexao(conn, query, rs);
        }
        return clientes;

    }

    @Override
    public List<Cliente> findByName(String name) {
        Connection conn = ConnectionFactory.abrirConexao();/* instanciando o objeto */
        String sql = "Select * from clientes where not ind_deletado and nome like ? ";
        List<Cliente> clientes = new ArrayList<Cliente>();
        Cliente c = null;

        try {
            query = conn.prepareStatement(sql);
            query.setString(1, "%" + name + "%");
            rs = query.executeQuery();
            while (rs.next()) {
                c = new Cliente(rs.getString("nome"), rs.getString("email"), rs.getString("telefone"));
                clientes.add(c);
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            ConnectionFactory.fecharConexao(conn, query, rs);
        }

        return clientes;
    }

/*Outros métodos da classe*/
Enter fullscreen mode Exit fullscreen mode

Injetando dependência através do construtor.

Ajustamos a classe ClienteDAO para que ela seja um component Spring e possa ser encontrada no start da aplicação por meio da marcação @Repository, agora faremos a injeção de dependência por meio do construtor na Classe ClienteController.
Desse modo, o contêiner do Spring, ao iniciar a aplicação, verifica que existe a necessidade de inserir na classe ClienteController alguma instância de objeto, no momento que a aplicação inicia, é feita a criação e os métodos de clienteDao já podem ser usados pela classe.

Classe ClienteController com anotação @Controller

@Controller
public class ClienteController {
    ClienteDAO clienteDao;

    public ClienteController(ClienteDAO clienteDao) {
        this.clienteDao = clienteDao;
    }
    public Cliente salvar(Cliente cliente) {
        return clienteDao.add(cliente);
    }
    public List<Cliente> listarTodos() {
        return clienteDao.list();
    }
    public Cliente alterar(Cliente cliente) {
        return clienteDao.add(cliente);
    }
    public void excluir(Cliente cliente) {
        clienteDao.remove(cliente);
    }
    public Cliente consultaPorId(Long id) {
        return clienteDao.findById(id);
    }
}

Enter fullscreen mode Exit fullscreen mode

Injetando dependência com o método Setter

Podemos usar um método Setter para fazer a injeção de dependência, a classe ClienteDAO, permanece da mesma forma, iremos alterar a classe ClienteController, não sendo mais necessário o construtor. Usamos um método Setter para configurar o atributo ClienteDAO.

@Autowired é uma anotação que usamos para fazer a configuração do método setter, é ela que o container spring vai procurar para fazer a injeção.

Classe ClienteController com injeção no método Setter

@Controller
public class ClienteController {
    private ClienteDAO clienteDao;

    @Autowired
    public void setClienteDao(ClienteDAO clienteDao) {
        this.clienteDao = clienteDao;
    }

    public Cliente salvar(Cliente cliente) {
        return clienteDao.add(cliente);
    }
    public List<Cliente> listarTodos() {
        return clienteDao.list();
    }
    public Cliente alterar(Cliente cliente) {
        return clienteDao.add(cliente);
    }
    public void excluir(Cliente cliente) {
        clienteDao.remove(cliente);
    }
    @GetMapping("findId")
    @ResponseBody
    public Cliente consultaPorId(Long id) {
        return clienteDao.findById(id);
    }
}

Enter fullscreen mode Exit fullscreen mode

Injetando dependência no atributo.

Nas configurações anteriores, alteramos o construtor e fizemos um método setter, isso já facilita o desenvolvimento e torna o código menos acoplado, mas podemos fazer de forma mais simples, veja:

Classe ClienteController com injeção no atributo

@Controller
public class ClienteController {
    @Autowired
    private ClienteDAO clienteDao;

    public Cliente salvar(Cliente cliente) {
        return clienteDao.add(cliente);
    }
    public List<Cliente> listarTodos() {
        return clienteDao.list();
    }
    public Cliente alterar(Cliente cliente) {
        return clienteDao.add(cliente);
    }
    public void excluir(Cliente cliente) {
        clienteDao.remove(cliente);
    }
    @GetMapping("findId")
    @ResponseBody
    public Cliente consultaPorId(Long id) {
        return clienteDao.findById(id);
    }
}

Enter fullscreen mode Exit fullscreen mode

Simplesmente colocando a anotação, vou poder acessar e usar os métodos do clienteDao sem nenhum problema, isso ocorre porque o IoC container inverte a ordem como as dependências são inseridas, pegando para si essa responsabilidade. Cada desenvolvedor tem seus hábitos e práticas, alguns preferem usar xml para deixar as classes mais limpas, outros usam o construtor, mas certamente a mais utilizada por mim é no atributo.

Você já conhecia o conceito de injeção de dependência e inversão de controller, consegui te ajudar? Deixe seu comentário.

Artigo escrito para o desafio do curso Especialista Spring Rest da AlgaWorks

Referências:
Documentação Spring
AlgaWorks - Injeção de dependência com Spring
DevMedia - Injeção de dependência JAVA JSR-330
Tutorials Point - Dependency Injection

Top comments (0)