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.

Listagem 1. Interface ILivroDao.
package com.mballem.tutorial.dao;
import com.google.inject.ImplementedBy;
import com.mballem.tutorial.entity.Livro;

import java.util.List;

/**
 * https://www.mballem.com/
 */
@ImplementedBy(LivroDao.class)
public interface ILivroDao {

    int save(Livro livro);

    int update(Livro livro);

    int remove(Long id);

    List findAll();
}

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.

Listagem 2. Interface ILivroService.
package com.mballem.tutorial.service;

import com.google.inject.ImplementedBy;
import com.mballem.tutorial.dao.ILivroDao;

/**
 * https://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.

Listagem 3. Classe LivroService.
package com.mballem.tutorial.service;

import com.google.inject.Inject;
import com.mballem.tutorial.dao.ILivroDao;

/**
 * https://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().

Listagem 4. Classe LivroController.
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;

/**
 * https://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 List findLivros() {
        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.

Listagem 5. Ativando o Guice – classe Main.
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;

/**
 * https://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);
            }
        });
    }
}
Listagem 6. Classe LivroFrame.
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.

Listagem 7. Classe MyModule.
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.

Listagem 8. Interface PersonDaoAnnotation.
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.

Listagem 9. Adicionando 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


Donwload projeto

Ballem

Marcio Ballem é bacharel em Sistemas de Informação pelo Centro Universitário Franciscano em Santa Maria/RS. Tem experiência com desenvolvimento Delphi e Java em projetos para gestão pública e acadêmica. Possui certificação em Java, OCJP 6.

Você pode gostar...