Padrão de Design de Injeção de Dependência em Java nos permite remover as dependências codificadas diretamente e tornar nossa aplicação fracamente acoplada, extensível e mantível. Podemos implementar a injeção de dependência em Java para transferir a resolução de dependência do tempo de compilação para o tempo de execução.
Padrão de Injeção de Dependência em Java
A injeção de dependência em Java parece ser difícil de entender apenas com a teoria, então vou apresentar um exemplo simples e depois veremos como usar o padrão de injeção de dependência para obter desacoplamento e extensibilidade na aplicação. Vamos supor que tenhamos uma aplicação onde consumimos EmailService
para enviar e-mails. Normalmente, implementaríamos isso da seguinte maneira.
package com.journaldev.java.legacy;
public class EmailService {
public void sendEmail(String message, String receiver){
// lógica para enviar e-mail
System.out.println("Email sent to "+receiver+ " with Message="+message);
}
}
A classe EmailService
contém a lógica para enviar uma mensagem de e-mail para o endereço de e-mail do destinatário. Nosso código da aplicação ficaria assim.
package com.journaldev.java.legacy;
public class MyApplication {
private EmailService email = new EmailService();
public void processMessages(String msg, String rec){
// realizar alguma validação de mensagem, lógica de manipulação, etc.
this.email.sendEmail(msg, rec);
}
}
O código do cliente que usará a classe MyApplication
para enviar mensagens de e-mail será semelhante ao seguinte.
package com.journaldev.java.legacy;
public class MyLegacyTest {
public static void main(String[] args) {
MyApplication app = new MyApplication();
app.processMessages("Hi Pankaj", "[email protected]");
}
}
À primeira vista, não parece haver nada de errado com a implementação acima. No entanto, a lógica do código acima possui certas limitações.
- A classe
MyApplication
é responsável por inicializar o serviço de e-mail e depois usá-lo. Isso leva a uma dependência codificada. Se quisermos mudar para algum outro serviço de e-mail avançado no futuro, será necessário alterar o código na classe MyApplication. Isso torna nossa aplicação difícil de estender e, se o serviço de e-mail for usado em várias classes, será ainda mais difícil. - Se quisermos estender nossa aplicação para fornecer um recurso de mensagens adicional, como SMS ou mensagem no Facebook, então precisaríamos escrever outra aplicação para isso. Isso envolverá alterações de código nas classes de aplicação e também nas classes de cliente.
- Testar a aplicação será muito difícil, pois nossa aplicação está criando diretamente a instância do serviço de e-mail. Não há como simular esses objetos em nossas classes de teste.
Alguém pode argumentar que podemos remover a criação da instância do serviço de e-mail da classe MyApplication
tendo um construtor que requer o serviço de e-mail como argumento.
package com.journaldev.java.legacy;
public class MyApplication {
private EmailService email = null;
public MyApplication(EmailService svc){
this.email=svc;
}
public void processMessages(String msg, String rec){
//faça alguma validação de mensagem, lógica de manipulação, etc.
this.email.sendEmail(msg, rec);
}
}
Mas, nesse caso, estamos pedindo que as aplicações cliente ou classes de teste inicializem o serviço de e-mail, o que não é uma boa decisão de design. Agora, vamos ver como podemos aplicar o padrão de injeção de dependência em Java para resolver todos os problemas com a implementação acima. A Injeção de Dependência em Java requer pelo menos o seguinte:
- Os componentes de serviço devem ser projetados com uma classe base ou interface. É melhor preferir interfaces ou classes abstratas que definiriam o contrato para os serviços.
- As classes consumidoras devem ser escritas em termos de interface de serviço.
- Classes de injeção que inicializarão os serviços e então as classes consumidoras.
Java Dependency Injection – Componentes de Serviço
Para o nosso caso, podemos ter MessageService
que declarará o contrato para implementações de serviço.
package com.journaldev.java.dependencyinjection.service;
public interface MessageService {
void sendMessage(String msg, String rec);
}
Agora, digamos que temos serviços de Email e SMS que implementam as interfaces acima.
package com.journaldev.java.dependencyinjection.service;
public class EmailServiceImpl implements MessageService {
@Override
public void sendMessage(String msg, String rec) {
// lógica para enviar email
System.out.println("Email sent to "+rec+ " with Message="+msg);
}
}
package com.journaldev.java.dependencyinjection.service;
public class SMSServiceImpl implements MessageService {
@Override
public void sendMessage(String msg, String rec) {
// lógica para enviar SMS
System.out.println("SMS sent to "+rec+ " with Message="+msg);
}
}
Nossos serviços de injeção de dependência em Java estão prontos e agora podemos escrever nossa classe consumidora.
Java Dependency Injection – Consumidor de Serviço
Não é necessário ter interfaces base para classes consumidoras, mas terei uma Consumer
interface declarando o contrato para classes consumidoras.
package com.journaldev.java.dependencyinjection.consumer;
public interface Consumer {
void processMessages(String msg, String rec);
}
A implementação da minha classe consumidora é como abaixo.
package com.journaldev.java.dependencyinjection.consumer;
import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplication implements Consumer{
private MessageService service;
public MyDIApplication(MessageService svc){
this.service=svc;
}
@Override
public void processMessages(String msg, String rec){
// fazer alguma validação de mensagem, lógica de manipulação, etc.
this.service.sendMessage(msg, rec);
}
}
Observe que nossa classe de aplicativo está apenas usando o serviço. Ela não inicializa o serviço, o que leva a uma melhor “separação de“` preocupbações”. Alémrazil disso, o usoian da interface de serviço-port nos permite testar facilugmente a aplicaçãouese, mockando o Message
Service e vinculObando os serviços emserve tempo de execução que, em vez de tempo a de compilação nossa. Agora estamos pr classeontos para escrever de classes de inje aplicção de dependência Javaação que irão inicial apenasizar o serviço e também utiliza as classes consumidor oas.
Injeção serviço de Dependência. Java – Classes Injet Eloras
Vamos tera uma interface MessageServiceInjector não com a inicial declizaaração de método o que retorna a classe serviço Consumer,. Agora o, para cada serviço que, teremos que le criar classes injetorasva como abaixo. Ag aora vamos ver uma como nossos aplicativos clientes melhor usarão o aplicativo “< com um programa simplesdi.
// Enviary email
// Enviar2 SMS
Como> você pode ver,separação de preocupações“. são criadas nos Al injetoresém. Além disso dis, se tivermosso que estender ainda, mais nosso o aplicativo uso para permit dair mensagens no Facebook interface, teremos que escre dever apenas classes de serviço serviço e injetoras nos. Portanto, a permite implementação de inje testção de dependênciaar resolveu o problema com facilmente a aplicação, faz dependênciaendo codificada e mock nos ajudou a do tornar nosso aplicativo flex Messageível e fácil deService estender. Agora e vamos ver como podemos test vincar facilmente nossa classeul de aplicativo mockandoando os injetores os e as classes de serviço serviços. em tempo de execução, em vez de tempo de compilação. Agora estamos prontos para escrever classes de injeção de dependência em Java que inicializarão o serviço e também as classes consumidoras.
Injeção de Dependência em Java – Classes Injetoras
Vamos ter uma interface MessageServiceInjector
com a declaração de um método que retorna a classe Consumer
.
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
public interface MessageServiceInjector {
public Consumer getConsumer();
}
Agora, para cada serviço, teremos que criar classes injetoras como mostrado abaixo.
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.EmailServiceImpl;
public class EmailServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
return new MyDIApplication(new EmailServiceImpl());
}
}
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.SMSServiceImpl;
public class SMSServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
return new MyDIApplication(new SMSServiceImpl());
}
}
Agora, vejamos como nossas aplicações clientes usarão a aplicação com um programa simples.
package com.journaldev.java.dependencyinjection.test;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.injector.EmailServiceInjector;
import com.journaldev.java.dependencyinjection.injector.MessageServiceInjector;
import com.journaldev.java.dependencyinjection.injector.SMSServiceInjector;
public class MyMessageDITest {
public static void main(String[] args) {
String msg = "Hi Pankaj";
String email = "[email protected]";
String phone = "4088888888";
MessageServiceInjector injector = null;
Consumer app = null;
//Enviar e-mail
injector = new EmailServiceInjector();
app = injector.getConsumer();
app.processMessages(msg, email);
//Enviar SMS
injector = new SMSServiceInjector();
app = injector.getConsumer();
app.processMessages(msg, phone);
}
}
Como você pode ver, nossas classes de aplicação são responsáveis apenas por utilizar o serviço. As classes de serviço são criadas nos injetores. Além disso, se precisarmos estender ainda mais nossa aplicação para permitir mensagens pelo Facebook, só será necessário escrever classes de serviço e classes injetoras. Dessa forma, a implementação de injeção de dependência resolve o problema com dependências codificadas e nos ajuda a tornar nossa aplicação flexível e fácil de estender. Agora, vejamos como podemos testar facilmente a nossa classe de aplicação fazendo mock dos injetores e das classes de serviço.
Injeção de Dependência em Java – Caso de Teste JUnit com Injetor e Serviço Falsos
package com.journaldev.java.dependencyinjection.test;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.injector.MessageServiceInjector;
import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplicationJUnitTest {
private MessageServiceInjector injector;
@Before
public void setUp(){
//falsificar o injetor com classe anônima
injector = new MessageServiceInjector() {
@Override
public Consumer getConsumer() {
//falsificar o serviço de mensagem
return new MyDIApplication(new MessageService() {
@Override
public void sendMessage(String msg, String rec) {
System.out.println("Mock Message Service implementation");
}
});
}
};
}
@Test
public void test() {
Consumer consumer = injector.getConsumer();
consumer.processMessages("Hi Pankaj", "[email protected]");
}
@After
public void tear(){
injector = null;
}
}
Como você pode ver, estou usando classes anônimas para falsificar as classes de injetor e serviço e posso facilmente testar os métodos da minha aplicação. Estou usando o JUnit 4 para a classe de teste acima, então certifique-se de que está no caminho de compilação do seu projeto se estiver executando a classe de teste acima. Usamos construtores para injetar as dependências nas classes da aplicação, outra maneira é usar um método setter para injetar dependências nas classes da aplicação. Para injeção de dependência por meio de método setter, nossa classe de aplicação será implementada como abaixo.
package com.journaldev.java.dependencyinjection.consumer;
import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplication implements Consumer{
private MessageService service;
public MyDIApplication(){}
//injeção de dependência por método setter
public void setService(MessageService service) {
this.service = service;
}
@Override
public void processMessages(String msg, String rec){
//realizar alguma validação de mensagem, lógica de manipulação etc.
this.service.sendMessage(msg, rec);
}
}
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.EmailServiceImpl;
public class EmailServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
MyDIApplication app = new MyDIApplication();
app.setService(new EmailServiceImpl());
return app;
}
}
Um dos melhores exemplos de injeção de dependência por setter é a interface Struts2 Servlet API Aware. Se deve usar injeção de dependência baseada em construtor ou baseada em setter é uma decisão de design e depende dos seus requisitos. Por exemplo, se minha aplicação não puder funcionar de forma alguma sem a classe de serviço, então eu preferiria DI baseada em construtor; caso contrário, eu optaria pela DI baseada em método setter para usá-la apenas quando realmente necessário. Injeção de Dependência em Java é uma maneira de alcançar Inversão de Controle (IoC) em nossa aplicação, movendo a vinculação de objetos do tempo de compilação para o tempo de execução. Podemos alcançar IoC através do Padrão de Fábrica, do Padrão de Projeto Template Method, do Padrão de Projeto Strategy e também do padrão Service Locator. Os frameworks Injeção de Dependência do Spring, Google Guice e CDI do Java EE facilitam o processo de injeção de dependência através do uso da API de Reflexão do Java e de anotações Java. Tudo o que precisamos fazer é anotar o campo, o construtor ou o método setter e configurá-los em arquivos ou classes de configuração.
Vantagens da Injeção de Dependência em Java
Alguns dos benefícios de usar a Injeção de Dependência em Java são:
- Separation of Concerns
- Redução de código boilerplate nas classes da aplicação, pois todo o trabalho de inicialização das dependências é gerenciado pelo componente injetor
- Componentes configuráveis tornam a aplicação facilmente extensível
- A realização de testes unitários é facilitada com objetos simulados (mock objects)
Desvantagens da Injeção de Dependência em Java
A injeção de dependência em Java também apresenta algumas desvantagens:
- Se usada em excesso, pode levar a problemas de manutenção, pois os efeitos das mudanças só são conhecidos em tempo de execução.
- A injeção de dependência em Java oculta as dependências da classe de serviço, o que pode resultar em erros em tempo de execução que teriam sido identificados em tempo de compilação.
Projeto de Injeção de Dependência para Download
Isso é tudo para o padrão de injeção de dependência em Java. É bom conhecê-lo e usá-lo quando temos controle sobre os serviços.