Entenda a injeção de dependência nos frameworks MVC

Todo mundo que utiliza algum framework MVC moderno já se deparou em algum momento com o conceito de Inversão de Controle (IoC). Com esse conceito, a classe não mais se preocupa em como conseguir suas dependências, mas sim em apenas trabalhar com elas. O principal jeito de conseguir isso é através da Injeção de Dependências (DI). Essa técnica, presente em diversos frameworks MVC, ajuda muito no desacoplamento e nos testes do nosso sistema.

Dois dos frameworks onde encontramos isso são o Spring MVC e o VRaptor.
No exemplo abaixo, temos um Controller em cada um desses Frameworks. Esse Controller depende de um DAO, que por sua vez tem como dependência uma Connection.

Usando o Spring:

Controlador:

@Controller
public class TarefasController {
    private final JdbcTarefaDao dao;

    @Autowired
    public TarefasController(JdbcTarefaDao dao) {
        this.dao = dao;
    }

    @RequestMapping(“adicionaTarefa”)
    public void adiciona(Tarefa tarefa) {
        dao.adiciona(tarefa);
       // ...
    }
}

DAO:

@Repository
public class JdbcTarefaDao {
    private final Connection connection;

    @Autowired
    public JdbcTarefaDao(DataSource dataSource) throws SQLException {
        this.connection = dataSource.getConnection();
    }
    // Resto do DAO, usando a connection sem instanciá-la em cada método
}

E usando o VRaptor:

Controlador:

@Controller
public class TarefasController {

    @Inject
    private final JdbcTarefaDao dao;

    @Post("/adicionaTarefa")
    public void adiciona(Tarefa tarefa) {
        dao.adiciona(tarefa);
        // ...
    }
}

DAO:

@RequestScoped
public class JdbcTarefaDao {

    @Inject
    private final Connection connection;

    // Resto do DAO, usando a connection sem instanciá-la em cada método
}

Repare que, quando a URL mapeada é chamada, o metódo adiciona é invocado pelo framework e, dentro dele, o DAO será usado. Mas onde ele está sendo instanciado, já que em nenhum momento nesses frameworks nós damos um new nessas dependências?

Para alguns desenvolvedores que olham esse código e não têm muito contato com essas técnicas, o processo de apenas receber as instâncias já criadas por alguém parece algo mágico e obscuro. A tendência é que eles acabem usando sem saber de onde vêm todas aquelas instâncias. Só que nada na computação é magia, tudo acontece por causa de um comando. Tudo é código.
A ideia de IoC é justamente fazer com que não nos preocupemos com a criação das instâncias, mas é sempre bom saber o que está acontecendo por trás dos panos.

Quando estamos iniciando no mundo do Java, sentimos a necessidade de entender como as coisas acontecem e podemos aproveitar um framework MVC caseiro para isso. No curso de Desenvolvimento Java para Web, desenvolvemos esse framework simples, que funciona da seguinte maneira: temos um servlet que recebe todas as requisições e, baseado em um parâmetro recebido, instancia uma lógica, que é definida por nós em uma classe que implementa a interface Logica. No caso, a interface contém apenas o método executa, que recebe um request e um response como parâmetros. O código desse framework é o seguinte:

@WebServlet("/mvc")
public class ControllerServlet extends HttpServlet {
    protected void service(HttpServletRequest request,
        HttpServletResponse response)
    throws ServletException, IOException {

        String parametro = request.getParameter("logica");
        String nomeDaClasse = "br.com.caelum.mvc.logica." + parametro;

        Class classe = Class.forName(nomeDaClasse);
        Logica logica = (Logica) classe.newInstance();
        String pagina = logica.executa(request, response);

        request.getRequestDispatcher(pagina).forward(request, response);
    }
}

E a lógica para fazer a remoção de um contato:

public class RemoveContatoLogic implements Logica {

    public String executa(HttpServletRequest req, HttpServletResponse res)
    throws Exception {

        long id = Long.parseLong(req.getParameter("id"));

        ContatoDao dao = new ContatoDao(); // O DAO é instanciado aqui.
        dao.exclui(id);

        return "lista-contatos.jsp";
    }
}

Note que, dentro do método executa, fazemos a instanciação do ContatoDAO para invocar o método exclui. Como vimos, isso não é uma boa prática, pois deixa as classes mais acopladas e dificulta na hora de realizar os testes de unidade.

A solução, como já indicamos, é apenas receber os objetos já instanciados. Queremos ter a seguinte lógica:

public class RemoveContatoLogic implements Logica {

    private ContatoDAO dao;

    public RemoveContatoLogic(ContatoDAO dao) {
        this.dao = dao;
    }

    public String executa(HttpServletRequest req, HttpServletResponse res)
    throws Exception {

        long id = Long.parseLong(req.getParameter("id"));

        dao.exclui(id); // O DAO não é instanciado no método executa

        return "lista-contatos.jsp";
    }
}

Para conseguir essas instâncias das dependências das lógicas, os frameworks MVC geralmente utilizam alguma biblioteca. Essas bibliotecas fazem a inversão do controle, através da Injeção de Dependências. Existem vários frameworks que fazem isso. Vamos modificar o nosso framework MVC simples para utilizar o Guice (pronuncia-se Juice).

Para tal, basta trocarmos o código da instanciação da lógica para parar de chamar o método newInstance() e passar a usar o Guice:

@WebServlet("/mvc")
public class ControllerServlet extends HttpServlet {
    protected void service(HttpServletRequest request,
        HttpServletResponse response)
    throws ServletException, IOException {

        String parametro = request.getParameter("logica");
        String nomeDaClasse = "br.com.caelum.mvc.logica." + parametro;

        // Usando o Guice para instanciar a lógica
        Injector injector = Guice.createInjector(new GuiceModule());
        Class classe = Class.forName(nomeDaClasse);
        Object logica = injector.getInstance(classe);

        String pagina = logica.executa(request, response);

        request.getRequestDispatcher(pagina).forward(request, response);
    }
}

A instância da classe GuiceModule é necessária para configurar a criação de uma Connection. Nela, herdamos a classe AbstractModule do Guice e configuramos o nosso injetor com o seguinte código:

public class GuiceModule extends AbstractModule {
    @Override
    protected void configure() {
        this.bind(Connection.class).to(ConnectionProvider.class);
    }
}

Por fim, temos que indicar ao Guice qual é o construtor da nossa lógica que ele deverá usar para instanciá-la e injetar as dependências. Fazemos isso anotando o construtor com @Inject:

public class RemoveContatoLogic implements Logica {

    private ContatoDAO dao;

    @Inject
    public RemoveContatoLogic(ContatoDAO dao) {
        this.dao = dao;
    }
    // metodo executa
}

Agora, quando a nossa lógica é invocada, o Guice se encarrega de criar as instâncias necessárias para que ela funcione. Nesse caso, isso significa conseguir uma instância de ContatoDAO antes mesmo de chamar o construtor da lógica. Ou seja, com apenas uma chamada ao Guice, conseguimos criar a lógica e todas as suas dependências.

Vale lembrar que os frameworks MVC vão mais além do que fazer a instanciação das dependências do construtor. Eles também recebem parâmetros direto no método da lógica, fazem conversão de tipos e trazem diversas outras facilidades para o dia a dia. Cabe ao desenvolvedor decidir qual framework mais o agrada, de acordo com os diferentes recursos de cada um. E você, já usa injeção de dependências no seu projeto?

5 Comentários

  1. Luiz Fagundes 23/02/2015 at 09:37 #

    Post essencial para todos os que trabalham com java!

  2. Paulo César da Silva 04/03/2015 at 10:53 #

    Acho importante ressaltar que alguns padrões de projetos são utilizados para que se possa obter a instância da classe sem a necessidade de declara-la. O Abstract Factory e o Singleton são alguns dos padrões de projetos utilizados para se conseguir tal funcionalidade.

  3. Diego Rosa 05/03/2015 at 07:42 #

    Ótimo post, só fiquei curioso em saber qual a estratégia que o Guide usa pra encontrar as depenências,

    será que ele lê as assinaturas dos métodos e usa reflections ? fiquei curioso mesmo rsrs

  4. Mauricio Carvalho 07/07/2016 at 08:40 #

    Muito boa a explicação..

    Super válida!

Deixe uma resposta