Gostaria de conhecer mais sobre injeção de dependências do ponto de vista do Spring Framework?
Esse é um conceito que ainda causa muita confusão na cabeça dos desenvolvedores. Quero aqui clarear esse assunto e trazer mais tranquilidade para quem está começando.
Continue lendo para aprender mais sobre:
- O que é injeção de dependências (Dependency Injection – DI)?
- Quais os pontos de injeção do Spring?
- Para o Spring, o que torna uma classe elegível a ser injetada e o que são métodos produtores?
- O que são os Qualificadores e como qualificar um bean Spring?
- Quais os escopos possíveis de um bean?
- Como utilizar a JSR 330?
Nos próximos minutos esse assunto já estará bem mais claro para você. :)
O que é injeção de dependências?
Injeção de dependências (ou Dependency Injection – DI) é um tipo de inversão de controle (ou Inversion of Control – IoC) que dá nome ao processo de prover instâncias de classes que um objeto precisa para funcionar.
Vamos supor uma classe como abaixo:
public class ClienteServico { private ClienteRepositorio repositorio; public void salvar(Cliente cliente) { this.repositorio.salvar(cliente); } ... }
Como a propriedade repositorio
é uma dependência de ClienteServico
, então, segundo o conceito de injeção de dependências, uma instância dessa propriedade deve ser provida.
Para manter o conceito de injeção não podemos utilizar a palavra reservada new
como abaixo:
public class ClienteServico { private ClienteRepositorio repositorio = new ClienteRepositorioJPA(); public void salvar(Cliente cliente) { this.repositorio.salvar(cliente); } ... }
Isso porque injeção é algo que espera-se que venha de fora para dentro.
A pergunta agora é: Por onde essa instância vai vir?
Isso vamos ver em um próximo tópico, sobre pontos de injeção.
O importante agora é entender que dessa forma nós conseguimos programar voltados para interfaces e, com isso, manter o baixo acoplamento entre as classes de um mesmo projeto.
Com certeza, essa característica é uma grande vantagem para a arquitetura do seu sistema. Sendo assim, esse é um conceito que merece a atenção da sua parte.
Pontos de injeção
Podemos considerar como pontos de injeção qualquer maneira que possibilite entregar a dependência para um objeto.
Os dois mais comuns são pelo construtor:
public class ClienteServico { private ClienteRepositorio repositorio; public ClienteServico(ClienteRepositorio repositorio) { this.repositorio = repositorio } ... } public class Main { public static void main(String... args) { ClienteRepositorio clienteRepositorio = new ClienteRepositorioJPA(); ClienteServico clienteServico = new ClienteServico(clienteRepositorio); ... } }
… e pelo método setter:
public class ClienteServico { private ClienteRepositorio repositorio; ... public void setClienteRepositorio(ClienteRepositorio repositorio) { this.repositorio = repositorio; } } public class Main { public static void main(String... args) { ClienteRepositorio clienteRepositorio = new ClienteRepositorioJPA(); ClienteServico clienteServico = new ClienteServico(); clienteServico.setClienteRepositorio(clienteRepositorio); ... } }
Provavelmente você já utilizou muito essas duas formas de injeção de dependências e esteja se perguntando:
“Alexandre, injeção de dependências é só isso?”
Sim, só. Esperava algo mais complicado?
A aplicação do conceito é bem simples mesmo, mas a grande questão vem quando queremos a automatização disso. E é aqui que o Spring entra.
Pontos de injeção no Spring
Apesar da aplicação do conceito ser simples, a automatização é bem complexa. Para a nossa felicidade, temos o Spring Framework que nos ajuda com esse trabalho.
Ele já tem toda a automatização da aplicação desse conceito de injeção de dependências.
Precisamos entender é como configurá-lo e quais são os pontos de injeção que ele usa.
Configurando o Spring
Nesse artigo, a configuração que irei fazer não é para o ambiente web.
Caso você queira mais detalhes sobre como configurar uma aplicação web com o Spring, você pode baixar o e-book Desenvolvimento Web com Spring Boot.
Aqui, vou utilizar essa configuração:
public class Aplicacao { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.scan(Aplicacao.class.getPackage().getName()); context.refresh(); // Utilizando a nossa classe ClienteServico com as // injeções feitas pelo Spring. ClienteServico servico = context.getBean(ClienteServico.class); servico.salvar(new Cliente(1, "Aluno João da Silva")); context.close(); } }
Basta que você crie a classe Aplicacao
no seu projeto. Repare que, para a configuração acima funcionar, é preciso que as outras classes como, por exemplo, a ClienteServico
estejam no mesmo pacote ou em um sub-pacote da classe Aplicacao
.
E a única dependência que é preciso ter no Maven é esta:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.5.RELEASE</version> </dependency>
Marcando um ponto como injetável no Spring
No Spring, para marcar os pontos de injeção dentro da sua classe, você utiliza a anotação @Autowired
.
Você pode utilizar essa anotação em:
- Propriedades;
- Construtores; e
- Métodos (mais comumente, os setters)
Utilizando a anotação nas propriedades, seria:
public class ClienteServico { @Autowired private ClienteRepositorio repositorio; ... }
No construtor:
public class ClienteServico { private ClienteRepositorio repositorio; @Autowired public ClienteServico(ClienteRepositorio repositorio) { this.repositorio = repositorio; } ... }
Por último, com métodos, seria assim:
public class ClienteServico { private ClienteRepositorio repositorio; ... // Caso você preferisse, esse método poderia se // chamar também "configurarRepositorio", mas // o mais comum é criar um método setter mesmo. @Autowired public void setRepositorio(ClienteRepositorio repositorio) { this.repositorio = repositorio; } }
É possível também misturar. Você pode, por exemplo, marcar uma propriedade e um construtor:
public class ClienteServico { @Autowired private Notificador notificador; private ClienteRepositorio repositorio; @Autowired public ClienteServico(ClienteRepositorio repositorio) { this.repositorio = repositorio; } ... }
O que torna uma classe elegível de ser injetada
Para que uma instância do tipo ClienteRepositorio
possa ser injetada em algum dos pontos de injeção é preciso que ela se torne um bean Spring.
Fazemos isso anotando ela com a anotação @Component
ou com qualquer uma de suas especializações:
- @Respository
- @Service
- @Controller
O Spring chama essas quatro anotações de estereótipos, sendo que as três últimas são como anotações “filhas” da anotação @Component
.
A anotação @Component
é a mais geral e as outras, que são @Repository
, @Service
e @Controller
, são para usos mais específicos em componentes de persistência, serviço e controlador, respectivamente.
Apesar de termos esses quatro estereótipos, eles não tem especificidades, com exceção da anotação @Repository
, que pode adicionar um pequeno comportamento relacionado a tradução das exceções de persistência.
Ainda assim, é aconselhável que você as utilize para especificar seus componentes de persistência, serviço e controlador, por dois motivos.
Primeiro que o uso delas torna mais fácil a associação com aspectos (AOP) e segundo porque não está descartada a possibilidade de, no futuro, elas ganhem tratamentos específicos do framework.
Inclusive a classe ClienteServico
utilizada no método main
da classe Aplicacao
também precisa estar anotada. No caso, o melhor seria a anotação @Service
.
Métodos produtores
As vezes precisamos ter beans cujas classes nós não podemos anotar.
Vamos supor uma classe ArquivoNuvem
que não está em nosso projeto e que gostaríamos que ela fosse passível de ser injetada. Como ela está em um outro projeto, não podemos anotar ela com estereótipo algum.
O que fazer nesse caso?
Podemos criar um método produtor utilizando a anotação @Bean
dentro de uma classe de configuração. Veja:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AplicacaoConfig { @Bean public ArquivoNuvem arquivoNuvem() { ArquivoNuvem nuvem = new ArquivoNuvem(); return nuvem; } }
Com a configuração acima, podemos injetá-la em nossa classe de serviço normalmente:
public class ClienteServico { @Autowired private ArquivoNuvem nuvem; @Autowired private ClienteRepositorio repositorio; }
Qualificando um bean Spring
Como falamos, uma das vantagens da injeção de dependências é o baixo acoplamento que conseguimos com a possibilidade de programar voltado a interfaces.
Mas aí pode vir uma pergunta:
“O que acontece se tivermos duas implementações da mesma interface?”
É lançada a exceção NoUniqueBeanDefinitionException
. Uma boa forma de resolver isso é qualificando nossos beans.
Veja como funciona:
public interface Notificador { void notificar(Mensagem mensagem); } @Component @Qualifier("importante") public class Email implements Notificador { @Override public void notificar(Mensagem mensagem) { ... } } @Component @Qualifier("urgente") public class Sms implements Notificador { @Override public void notificar(Mensagem mensagem) { ... } }
Dessa forma, podemos agora injetar as duas implementações em nossas classes:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; @Service public class ClienteServico { @Qualifier("importante") @Autowired private Notificador importante; @Qualifier("urgente") @Autowired private Notificador urgente; }
Ainda é possível utilizar qualificadores bem mais específicos do que isso, mas, para o objetivo do artigo que é injeção de dependências, isso é o suficiente.
Os escopos de um bean Spring
Podemos controlar o tempo de vida de um bean Spring. Utilizamos o conceito de escopos para isso.
No Spring temos sete escopos ativos, sendo que cinco são somente para o ambiente web.
Os escopos:
- singleton (o padrão); e
- prototype;
… são escopos gerais, e os escopos:
- request;
- session;
- globalSession;
- application; e
- websocket
… servem somente ao ambiente web.
Tem mais um escopo que é o “thread”. Este não está ativo por padrão, mas é bem simples ativá-lo.
Você ainda pode criar os seus próprios escopos caso precise. Assim como fizemos na configuração do escopo de “view” utilizado na integração do JSF com o Spring.
Para especificar um escopo, nós colocamos o nome dele dentro da anotação @Scope
. Olhe só:
@Scope("prototype") @Service public class ClienteServico { ... }
JSR-330 e os beans Spring
Para quem quer ou precisa utilizar a JSR-330, o Spring possui compatibilidade.
Basta trocar a anotação @Autowired
por @Inject
e a anotação @Component
pela @Named
.
Veja como ficaria:
import javax.inject.Named; @Named public class ClienteRepositorioJPA implements ClienteRepositorio { ... } import javax.inject.Inject; import javax.inject.Named; @Named public class ClienteServico { @Inject private ClienteRepositorio repositorio; ... }
Lembrando que é preciso também incluir a dependência no Maven:
<dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency>
Conclusão
Espero que o assunto “injeção de dependências com Spring” tenha clareado em sua mente. :)
Foi um artigo onde conversamos sobre o que é injeção de dependências e as características que o Spring adiciona a esse conceito como, por exemplo, a forma como criamos beans, os qualificadores deles e os escopos que eles podem assumir.
Para quem quiser avançar mais um passo, tenho um convite aqui que é para o download do e-book Desenvolvimento Web com Spring Boot:
No mais, um abraço pra você e até uma próxima!
PS: o código-fonte do artigo está disponível em nosso GitHub: http://github.com/algaworks/artigo-spring-injecao-de-dependencias
Olá,
o que você achou deste conteúdo? Conte nos comentários.