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);
}
}
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...*/
}
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.
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*/
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);
}
}
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);
}
}
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);
}
}
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)