Injeção de Dependências com Guice (DI)
Guice é um projeto interno da Google que foi desenvolvido inicialmente para uso em suas próprias aplicações. Com o tempo, a Google disponibilizou o Guice para uso geral da comunidade Java. O objetivo do Guice é realizar os conceitos do padrão de projeto conhecido como Injeção de Dependências. A Injeção de dependência ou, Depedency Injection ou ainda apenas DI é um padrão de projeto utilizado para manter o baixo acoplamento entre classes ou módulos do sistema. O objetivo principal é fazer com que uma classe não tenha conhecimento de como instanciar um objeto de um tipo do qual é dependente. Mas quando uma classe é dependente de um objeto? Imagine a classe A
como variável de instancia da classe B
. Quando você instanciar a classe B
terá que ter um objeto instanciado da classe A
também, caso não tenha, poderá ter uma exceção do tipo nullpointer e também não terá acesso aos métodos de A
. Para instanciar A
em B
, basta fazer um: A a = new A();
O processo de DI abstrai do programador a necessidade da instancia no código, ficando ele responsável por este feito. Essa inversão de papeis é conhecida como Inversão de Controle. Um framework muito conceituado e já explorado algumas vezes aqui no blog é o Spring. Neste tutorial vamos explorar o framework da Google, e conhecer o básico necessário para iniciar um projeto com o Guice.
Tempos atrás postei a vídeo aula JTable com Banco de Dados, o qual agora servirá coma base para a uso do Guice.
1. Configurando o Guice
Para utilizar o Guice é necessário baixar uma biblioteca e adicioná-la ao projeto. Atualmente, pelo menos quando preparei este tutorial, a versão mais atual era a 3.0. Você pode baixá-la no link: http://code.google.com/p/google-guice/. Para quem usa o Maven, pode baixar adicionando a seguinte configuração no arquivo pom.xml
:
<dependency> <groupId>com.google.inject</groupId> <artifactId>guice</artifactId> <version>3.0</version> </dependency>
No código fonte é necessário marcar as classes que serão injetadas. O Guice trabalha especificamente com interfaces. Diferentemente do Spring, onde podemos injetar uma classe concreta ou interfaces, o Guice aceita apenas que interfaces sejam injetadas. É possível fazer a injeção de dependências pelo construtor da classe, pelos atributos ou métodos set()
. Veja na Listagem 1 a interface ILivroDao
. Observe que no topo da assinatura da interface temos a anotação @ImplementedBy
. Esta anotação recebe como par metro a classe que implementa a interface, neste exemplo a classe concreta LivroDao
. Na classe LivroDao
não é necessária nenhuma configuração para o processo de DI, sendo assim, não irei abordá-la.
package com.mballem.tutorial.dao; import com.google.inject.ImplementedBy; import com.mballem.tutorial.entity.Livro; import java.util.List; /** * http://www.mballem.com/ */ @ImplementedBy(LivroDao.class) public interface ILivroDao { int save(Livro livro); int update(Livro livro); int remove(Long id); ListfindAll(); }
No tutorial JTable com Banco de Dados não foi adicionada uma interface para a classe LivroService
, mas agora, para usar o Guice, isto deverá ser feito. Veja na Listagem 2 a interface ILivroService
. Nesta interface teremos apenas um método, getDao()
, que fornecerá acesso a todos os métodos da interface ILivroDao
através da classe concreta LivroService
.
package com.mballem.tutorial.service; import com.google.inject.ImplementedBy; import com.mballem.tutorial.dao.ILivroDao; /** * http://www.mballem.com/ */ @ImplementedBy(LivroService.class) public interface ILivroService { ILivroDao getDao(); }
Na Listagem 3 injetamos em LivroService
a interface ILivroDao
. Para isso, é necessário usar a anotação @Inject
. A injeção foi realizada através do construtor da classe, porém, como já citado, poderia ser feita diretamente no atributo privado ou em um método set()
. Observe que em momento algum foi declarada uma instancia da classe dao no service, quem fará isso quando for necessário será o framework de DI.
package com.mballem.tutorial.service; import com.google.inject.Inject; import com.mballem.tutorial.dao.ILivroDao; /** * http://www.mballem.com/ */ public class LivroService implements ILivroService { private ILivroDao dao; @Inject public LivroService(ILivroDao dao) { this.dao = dao; } public ILivroDao getDao() { return dao; } }
Vamos agora injetar ILivroService
na classe LivroController
, conforme Listagem 4. Novamente, a injeção de dependência é realizada com a anotação @Inject
. Repare que todos os métodos da classe de persistência são acessados através do service e do método getDao()
, sem a necessidade de fazer um new LivroService()
.
package com.mballem.tutorial.view.controller; import com.google.inject.Inject; import com.mballem.tutorial.entity.Livro; import com.mballem.tutorial.service.ILivroService; import com.mballem.tutorial.service.LivroService; import java.util.List; /** * http://www.mballem.com/ */ public class LivroController { private ILivroService service; @Inject public LivroController(ILivroService service) { this.service = service; } public int addLivro(Livro livro) { return service.getDao().save(livro); } public int updateLivro(Livro livro) { return service.getDao().update(livro); } public int excluirLivro(Long id) { return service.getDao().remove(id); } public ListfindLivros() { return service.getDao().findAll(); } }
Por fim, é necessário ativar o framework, para isso veja a classe Main
na Listagem 5. Para iniciar os serviços do Guice, é preciso criar um injetor através do método estático Guice.createInjector()
e adicionar o retorno em um objeto do tipo Injector. A partir do Injector, teremos acesso a instancia de qualquer classe que implementa as interfaces com as anotações @ImplementedBy
. Neste exemplo recuperamos a instancia de LivroService
e adicionamos no construtor da classe LivroController
. Passamos a instancia de LivroController
para a classe da interface gráfica, LivroFrame
da Listagem 6.
Como o injetor adicionou uma instancia para LivroService
e esta classe possui uma dependência do tipo ILivroDao
, a dependência automaticamente também receberá instancias sempre que necessário.
package com.mballem.tutorial; import com.google.inject.Guice; import com.google.inject.Injector; import com.mballem.tutorial.service.ILivroService; import com.mballem.tutorial.view.controller.LivroController; import com.mballem.tutorial.view.frame.LivroFrame; /** * http://www.mballem.com/ */ public class Main { public static void main(String[] args) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { // inicializa o injetor do guice Injector injector = Guice.createInjector(); // recupera uma instancia de LivroService LivroController livroController = new LivroController( injector.getInstance(ILivroService.class) ); // adiciona na classe LivroFrame uma instancia de LivroService new LivroFrame(livroController); } }); } }
public class LivroFrame extends JFrame { private LivroController livroController; public LivroFrame(LivroController livroController) throws HeadlessException { super("Cadastro de Livros"); this.livroController = livroController; } }
Mais informações sobre o Guice podem ser encontradas na página oficial do framework na seção Referencias. Além das anotações citadas, o Guice oferece outras anotações como a @Singleton
, para transformar uma classe no padrão Singleton.
2. Classe AbstractModule
É possível também ter uma interface e diversas classes concretas que a implemente, porem deste modo, não se é capaz de fazer uso da anotação @implementBy
, porque ela aceita apenas uma classe como par metro e não é possível adicionar mais de uma anotação deste tipo por classe. Para isso, se deve usar uma classe abstrata chamada AbstractModule
e implementar seu método configure()
. Veja um breve exemplo na Listagem 7 desta configuração. Vamos supor que tivéssemos a interface IDao
e ela fosse implementada pelas classes PersonDao
, CarDao
e PhoneDao
.
Note que o método bind()
recebe a interface como parametro. Este método fornece acesso ao método annotatedWith()
e ao método to()
. O método to()
recebe como parametro a classe concreta, que implementa a interface. Já o método annotatedWith()
deve receber como parametro uma interface de anotação especial para cada classe concreta.
public class MyModule extends AbstractModule { @Override protected void configure() { bind(IDao.class) .annotatedWith(PersonDaoAnnotation.class) .to(PersonDaoAnnotation.class); bind(IDao.class) .annotatedWith(CarDaoAnnotation.class) .to(CarDaoAnnotation.class); bind(IDao.class) .annotatedWith(PhoneDaoAnnotation.class) .to(PhoneDaoAnnotation.class); } }
Na Listagem 8 o exemplo da interface de anotação PersonDaoAnnotation
. Deve existir uma interface desse tipo para cada classe que injeta a interface IDao
.
package com.mballem.tutorial.guicemongo.converter.module; import com.google.inject.BindingAnnotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.PARAMETER}) @BindingAnnotation public @interface PersonDaoAnnotation { }
Por fim, na classe que criará o injetor, se faz necessário indicar a MyModule
para que o framework conheça a configuração. Veja na Listagem 9 como adicionar MyModule
no injetor.
public static void main(String[] args) { Injector injector = Guice.createInjector(new MyModule()); IService service = injector.getInstance(IService.class); ... }
Não deixe de visitar a página oficial do Guice para descobrir todos seus recursos. Este framework pode também ser integrado a Servlet, JPA, Struts 2, GWT, etc.
Referencias
- Página oficial do Guice – http://code.google.com/p/google-guice/
- Versões do Guice para download – http://code.google.com/p/google-guice/downloads/list
- Guia do usuário Guice – http://code.google.com/p/google-guice/wiki/Motivation
- Guice Java-Doc – http://google-guice.googlecode.com/git/javadoc/packages.html
- Tutorial Injeção de dependencias by Google – http://www.urubatan.com.br/tutorial-google-guice-injecao-de-dependencias-by-google/