Injeção de Dependência em Java

Injeção de dependência (Depedency Injection ou apenas DI) é um design pattern utilizado para manter o acoplamento fraco entre classes ou módulos do sistema. O foco principal é fazer com que uma classe não tenha conhecimento de como instanciar um objeto de um tipo do qual é dependente.

Inject Coffee - Injeção Dependência em Java

Neste post vou apresentar as principais ferramentas utilizadas com essa finalidade em Java, mas antes uma explicação inicial sobre o assunto. Dê uma olhada no código Java abaixo:

public class CaixaMercado {

 private CatalogoProdutos catalogoProdutos = new CatalogoProdutosBD();

 private List<Produto> itensCompraAtual;

 public void iniciarNovaCompra() {
  itensCompraAtual = new ArrayList<Produto>();
 }

 public void produtoPassadoNoLeitor(String codigoDeBarras) throws ProdutoInexistenteException {
  if (itensCompraAtual == null) {
   throw new IllegalStateException("Compra não foi iniciada.");
  }

  Produto produto = catalogoProdutos.buscarPeloCodigoBarras(codigoDeBarras);

  if (produto == null) {
   throw new ProdutoInexistenteException();
  }

  itensCompraAtual.add(produto);
 }

 public List<Produto> finalizarCompra() {
  // faz algo

  List<Produto> itensCompraAnterior = itensCompraAtual;
  itensCompraAtual = null;

  return itensCompraAnterior;
 }
}

Neste exemplo quando um objeto da classe CaixaMercado é instanciado, também é instanciado um objeto do tipo CatalogoProdutosBD (veja na linha 3). Isso torna a classe CaixaMercado dependente da implementação do catálogo lido a partir do banco de dados. Você pode pensar: “Mas nunca vou utilizar outra fonte de dados mesmo.”. E com relação aos testes unitários?  E se a classe CatalogoProdutosBD precisasse receber algum parâmetro específico?

Resolvendo “manualmente”

A opção mais simples para resolver este problema é injetar o catálogo de produtos no construtor da classe CaixaMercado. Dessa forma, a classe CaixaMercado fica totalmente desacoplada da implementação de CatalogoProdutos, pois não tem conhecimento se o produto será consultado a partir da base de dados ou arquivo texto, por exemplo. Neste caso, também poderia ser utilizado um método setter para a injeção.

public class CaixaMercado {

 private CatalogoProdutos catalogoProdutos;

 private List<Produto> itensCompraAtual;

 public CaixaMercado(CatalogoProdutos catalogoProdutos) {
  this.catalogoProdutos = catalogoProdutos;
 }

//...
}

Essa técnica pode ser aplicada para ligar diferentes componentes de sua aplicação com o objetivo diminuir o acoplamento entre eles. Para facilitar a utilização de DI, existem vários frameworks que auxiliam na injeção de objetos através de metadados, como XML e annotations. A seguir apresento as principais opções utilizadas na tecnologia Java.

Spring

O Spring é um framework de IoC (Inversão de Controle)  que utiliza a técnica de Injeção de Dependência. Para o código de exemplo, a configuração através de XML seria:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
 <bean id="CatalogoProdutos" class="org.exemplo.di.CatalogoProdutosBD" />
 <bean id="CaixaMercado" class="org.exemplo.di.spring.CaixaMercado">
  <property name="catalogoProdutos" ref="CatalogoProdutos" />
 </bean>
</beans>

Nas últimas versões do Spring é possível utilizar apenas annotations no lugar do XML para realizar essa configuração, conforme mostra o código abaixo:

public class CaixaMercado {

 @Autowired
 private CatalogoProdutos catalogoProdutos;

 private List<Produto> itensCompraAtual;

 //...
}

 

@Component
public class CatalogoProdutosBD implements CatalogoProdutos {

 @Override
 public Produto buscarPeloCodigoBarras(String codigo) {
  //...
 }
}

O escopo das instâncias gerenciadas pelo Spring podem ser definidos por XML com o atributo scope ou através da annotation @Scope. Os valores possíveis são:

  • singleton: Instância única por IoC container
  • prototype: Nova instância em cada requisição
  • request: Nova instância por requisição HTTP
  • session: Nova instância por sessão HTTP
  • globalSession: Nova instância por sessão HTTP global (utilizado com portlets)

O Spring também define as seguintes especializações da annonation @Component para identificar diferentes tipos de objetos:

  • @Controller
  • @Service
  • @Repository

Como todo desenvolvedor Java deve saber, o Spring é muito mais do que isso. O objetivo aqui foi apresentar apenas como trabalhar com injeção de dependência com este framework.

Guice

Outra opção é o Guice (pronuncia-se “juice”), que é um framework de injeção de dependências desenvolvido pelo Google. De acordo com o site do projeto, o próprio Google o utiliza em aplicações de missão crítica desde 2006. Abaixo o mesmo exemplo utilizando o Guice:

public class CaixaMercado {

 @Inject
 private CatalogoProdutos catalogoProdutos;

 private List<Produto> itensCompraAtual;

 //...
}

Não é necessária nenhuma mudança na classe CatalogoProdutosBD em relação a implementação original. Entretanto, é necessário criar uma classe que define quais são as implementações a serem injetadas:

public class CaixaMercadoModule extends AbstractModule {

 @Override
 protected void configure() {
  bind(CatalogoProdutos.class).to(CatalogoProdutosBD.class);
 }

}

O Guice pode ser utilizado em projetos web tornando possível definir o escopo das instâncias dos tipos que são injetados. Para isso são disponibilizadas as seguintes annotations:

  • @Singleton
  • @SessionScoped
  • @RequestScoped

Seam

O JBoss Seam  surgiu com o objetivo inicial de realizar uma melhor integração entre o JSF e EJB 3, porém atualmente é um framework muito completo e pode ser utilizado sem EJB ou até mesmo com o Apache Wicket no lugar do JSF, por exemplo. Até a versão 2.x utilizaríamos da seguinte maneira:

public class CaixaMercado {

 @In
 private CatalogoProdutos catalogoProdutos;

 private List<Produto> itensCompraAtual;

 //...
}

 

@Name("catalogoProdutos")
public class CatalogoProdutosBD implements CatalogoProdutos {

 @Override
 public Produto buscarPeloCodigoBarras(String codigo) {
  //...
 }
}

A partir do Seam foram propostas e aprovadas a JSR 330: Dependency Injection for Java e a JSR 299: Contexts and Dependency Injection for the Java EE platform. A implementação de referência da JSR 299 é o projeto Weld, que foi baseado no core do Seam. A versão 3 do Seam utiliza o Weld e adiciona funcionalidades especificas que não estão padronizadas.

JSR 330: Dependency Injection for Java

A JSR 330 padroniza as annotations para a injeção de dependências na linguagem Java. As últimas versões do Spring, Guice e Seam são compatíveis com este padrão. Veja abaixo como fica o exemplo:

public class CaixaMercado {
 @Inject
 private CatalogoProdutos catalogoProdutos;

 private List<Produto> itensCompraAtual;

 //...
}

 

@Named("catalogoProdutos")
public class CatalogoProdutosBD implements CatalogoProdutos {

 @Override
 public Produto buscarPeloCodigoBarras(String codigo) {
  //...
 }
}

As annotations criadas para injeção de dependências através da JSR 299 estão todas no pacote javax.inject.*. Essas annotations são:

  • @Inject: Injeta um objeto em atributo, construtor ou método
  • @Named: Qualificador de tipo a ser injetado através de uma string
  • @Singleton: Instância única do tipo a ser injetado
  • @Qualifier: Identifica annotation que qualifica tipo a ser injetado
  • @Scope: Identifica annotation que define um tipo de escopo

Na prática as 2 últimas annotations (@Qualifier e @Scope) são utilizadas pelas implementações (Seam, Guice, etc.) ou quando se deseja realizar alguma customização específica.

JSR 299: CDI for the Java EE platform

Essa JSR descreve várias outras funcionalidades além da injeção de dependências. Para este post vou somente apresentar as annotations que definem os possíveis escopos dos objetos criados para serem injetados, que complementam as especificados pela JSR 330:

  • @ApplicationScoped
  • @SessionScoped
  • @RequestScoped
  • @ConversationScoped

EJB 3

Quem utiliza EJB 3 também tem disponível annotations para injeção de dependências. Essas annotations são:

  • @EJB: Injeta um session bean
  • @PersistenceContext: Injeta o EntityManager gerenciado
  • @PersistenceUnit: Injeta o EntityManagerFactory
  • @Resource: Injeta recurso disponível na árvore JNDI (Data Source, JMS, etc.)
  • @WebServiceRef: Injeta referência para um webservice

Utilizando essas annotations, o container irá realizar o lookup dos objetos e injetá-los. Se fossemos utilizar EJB para o exemplo, poderíamos considerar a implementação de CaixaMercado como um Session Bean Stateful e a implementação do CatalogoProduto como Stateless. Abaixo o código:

@Local
public interface CaixaMercado {
 public void iniciarNovaCompra();
 public void produtoPassadoNoLeitor(String codigoDeBarras);
 public List<Produto> finalizarCompra();
}

 

@Stateful
public class CaixaMercadoBean implements CaixaMercado {

 @EJB
 private CatalogoProdutos catalogoProdutos;

 private List<Produto> itensCompraAtual;

 //...
}

 

@Local
public class CatalogoProdutos
 public Produto buscarPeloCodigoBarras(String codigo);
}

 

@Stateless
public class CatalogoProdutosBD implements CatalogoProdutos {

@Override
public Produto buscarPeloCodigoBarras(String codigo) {
//...
}
}

Lembrando que também é possível utilizar implementações da JSR 299 em conjunto com o EJB 3.

Outras opções

Abaixo outras opções de ferramentas que podem ser utilizadas para auxiliar a injeção de dependência:

Se você souber de mais alguma me avisa que eu adiciono aqui :)

Conclusão

Nesse post descrevi o que é, por que e como utilizar o conceito Injeção de Dependência em Java. Cada ferramenta apresentada aqui possui muito mais funcionalidades para injeção de objetos e não se limitam a somente essa função. Escolha a(s) sua(s) e bons estudos!

Referências