terça-feira, 17 de julho de 2012

Injeção de Dependências com CDI

Uma das especificações do Java EE indispensáveis em meus projetos é sem sombra de dúvidas CDI. Nesse artigo, usaremos o Weld para manipularmos dependêcias.
Configurando o CDI:

Para rodar o CDI, você deve rodar seu projeto em um servidor de aplicação Java EE 6 (a especificação do CDI não inclui Servlet Containers. Com excessão do Tomcat e do Jetty). O Weld já vem incluído no GlassFish e no JBoss.

Configurando o Listener:

Vá até o arquivo web.xml e adicione o Listener do Weld:

    org.jboss.weld.environment.servlet.Listener

O próximo passo é criar uma pasta META-INF no diretório source. Caso você use Eclipse, clique com botão direito em src > New > Other > Folder. Digite META-INF em Folder Name e clique em Finish.

Dentro da pasta META-INF, vamos criar um arquivo chamado beans.xml. Este arquivo serve para indicar ao CDI que essa aplicação é elegível à injeção de dependências. Entre outras coisas também fazemos configurações neste arquivo. Como os interceptadores do Seam Persistence, por exemplo. Como não iremos fazer configuração, apenas criaremos um arquivo vazio (retire inclusive o esteriótipo do arquivo xml) chamado beans.xml.

Pronto! O CDI já está configurado em seu projeto.

Agora, vamos a prática...

Suponha que eu tenha uma classe DAO que tenha uma conexão básica JDBC para adicionar tarefas no banco de dados.

public class TarefaDAO {
 
 private Connection cnn = DriverManager.getConnection("jdbc:mysql://localhost/meubanco", "root", "123");
 
 public void adiciona(Tarefa tarefa){
  // adiciona tarefa no banco
 }
 
}

E abaixo, uma servlet que irá fazer uso desta classe.

@WebServlet(value="/adicionar")
public class AdicionaTarefa extends HttpServlet {

 private TarefaDAO dao;

 public void service(HttpServletRequest req, HttpServletResponse res) {

  Tarefa tarefa = new Tarefa();
  tarefa.setTitulo(req.getParameter("titulo"));
  tarefa.setDescricao(req.getParameter("descricao"));
  dao.adiciona(tarefa);
  
 }
}

O que há de errado com essa abordagem? Duas coisas. Primeiro: ao executar essa servlet, o usuário irá receber um NullPointerException na linha 11. Eu precisaria de uma instância de TarefaDAO para conseguir rodar o método adiciona. A abordagem abaixo resolve este problema.
private TarefaDAO dao = new TarefaDAO();

Com isso, nosso sistema funcionará. Mas ainda há um problema. Antes de mostrar a solução, quero levantar a seguinte questão. Para que fazer injeção de dependência? Uma injeção deve ser feita, quando uma classe conhece algo além do que ela deveria. Como assim? Por exemplo, a classe TarefaDAO deve saber como se cria uma conexão em JDBC para poder utiliza-la em seus métodos. Vamos supor que eu tenha mais classes DAO no meu sistema e eu resolva querer trocar o endereço do host onde está o banco de dados. Vocês vão me responder: "Use o Design Pattern Factory!". Essa alternativa não é 100% válida neste caso, veremos porque.

Abaixo, temos uma classe factory para o Connection:
public class ConnectionFactory {
 
 public static Connection getConnection() throws SQLException, Exception {   
  Class.forName("com.mysql.jdbc.Driver");
  return DriverManager.getConnection("jdbc:mysql://localhost/meubanco",
    "root", "");
 }
}

E sua aplicação no TarefaDAO:

public class TarefaDAO {
 
 private Connection cnn = ConnectionFactory.getConnection();
 
 public void adiciona(Tarefa tarefa){
  // adiciona tarefa no banco
 }
 
}

Suponha-se que eu precise fazer conexão com outro banco de dados. Como ficaria essa implementação na minha classe?

public class ConnectionFactory {

 public static Connection getConnection(String nomeDoBanco)
   throws SQLException, Exception {

  if ("mysql".equals(nomeDoBanco)) {

   Class.forName("com.mysql.jdbc.Driver");
   return DriverManager.getConnection(
     "jdbc:mysql://localhost/meubanco", "root", "");
  } else if ("postgres".equals(nomeDoBanco)) {

   return DriverManager.getConnection(
     "jdbc:postgresql://localhost/meubanco", "root", "");
  }

  return null;

 }
}

Com essa abordagem, um erro de compilação seria lançado na classe TarefaDAO e em todas as outras classes que usam ConnectionFactory. Isso porque, quando uma classe sabe demais sobre outras classes significa que elas estão com alto nível de acoplamento. Para resolver esse problema, devemos dizer que as classes DAO não pedem mais informações sobre Connection. E sim, as informações são entregues através de injeções. Como faremos isso? Simples, anotaremos o método getConnection com @Produces. Anotação do CDI que indica o método que ele deverá chamar todas as vezes que uma classe injetar de uma Connection.

public class ConnectionFactory { 

   @Produces
 public static Connection getConnection()
   throws SQLException, Exception {  

   Class.forName("com.mysql.jdbc.Driver");
   return DriverManager.getConnection(
     "jdbc:mysql://localhost/meubanco", "root", "");
  

 }
}

E na classe TarefaDAO, injetaremos uma Connection.
public class TarefaDAO {

 @Inject
 private Connection cnn;
 
 public void adiciona(Tarefa tarefa){
  // adiciona tarefa no banco
 }
 
}

Agora, como resolver o problema de múltiplos bancos?

Qualifiers do CDI

Uma coisa muito importante (mas que você já deve ter percebido) sobre o CDI, é que ele segue o padrão Type Safety. O que é isso? Ele não confia em identificadores para referências, e sim se baseia nos tipos. O método getConnection retorna uma Connection. Todas as vezes que uma classe injetar uma Connection o CDI invocará este método. Mas eu preciso de várias Connections diferentes para diferentes casos. Para isso servem os qualifiers. Eles fazem a distinção de diferentes métodos de fabricação.

Primeiro criarmos um qualifier, precisamos criar uma nova anotação. Vamos chama-la de MySQL e PostGRESQL:
@Retention(RetentionPolicy.RUNTIME) // Dizemos aqui que a anotação irá servir em tempo de execução
public @interface MySQL {

}
@Retention(RetentionPolicy.RUNTIME) // Dizemos aqui que a anotação irá servir em tempo de execução
public @interface PostGRESQL {

}

Agora precisamos dizer ao CDI, que essas anotações são qualifiers. Anotando-as com @Qualifier:

@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MySQL {

}
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface PostGRESQL {

}
Agora vamos criar métodos para conexão para nossos bancos e anota-las com suas respectivas anotações:
public class ConnectionFactory {

    @MySQL
    @Produces
 public static Connection getConnectionMYSQL() throws SQLException {

  return DriverManager.getConnection("jdbc:mysql://localhost/meubanco",
    "root", "");
 }

    @PostGRESQL
    @Produces
 public static Connection getConnectionPostGreSQL() throws SQLException {

  return DriverManager.getConnection(
    "jdbc:postgresql://localhost/meubanco", "root", "");

 }

}
Pronto! Agora vamos injeta-la na classe TarefaDAO:
public class TarefaDAO {

 @Inject
 @MySQL
 private Connection cnnMySQL;
 
 @Inject
 @PostGRES
 private Connection cnnPostGRES;

 public void adiciona(Tarefa tarefa) {
  // adiciona tarefa no banco
 }

}


Disposes do CDI:

Mais um detalhe. Onde fecharemos nossa conexão? Uma conexão ao banco de dados tem custo muito alto para simplesmente ignorarmos esse processo. O CDI pode cuidar disso para nós com os métodos Disposes.

Para criar um método Dispose basta que ele receba uma Connection e esse parâmetro seja anotado com @Disposes. Então, adicionaremos o seguinte método em nossa ConnectionFactory:

public static void close(@Disposes @MySQL Connection cnn){
   cnn.close();
}
O mesmo também é valido para o método produtor de conexão PostGRES.

Para que isso tenha efeito, precisamos anotar nossos métodos produtores com @RequestScoped. Do contrário, o mesmo terá escopo Dependant. Ou seja, toda vez que se precisar de uma Connection será criada uma nova. Quando na verdade queremos que em cada request apenas uma seja criada.
public class ConnectionFactory {

    @MySQL
    @Produces
    @RequestScoped
 public static Connection getConnectionMYSQL() throws SQLException {

  return DriverManager.getConnection("jdbc:mysql://localhost/meubanco",
    "root", "");
 }

    @PostGRESQL
    @Produces    
    @RequestScoped
public static Connection getConnectionPostGreSQL() throws SQLException {

  return DriverManager.getConnection(
    "jdbc:postgresql://localhost/meubanco", "root", "");

 }

}

Injetando Java Beans:

Aproveitando o gatilho, podemos ainda injetar TarefaDAO dentro da nossa Servlet. Partindo do principio que TarefaDAO possui um construtor sem argumentos, não iremos precisar do método produtor. Basta anotar o atributo do tipo TarefaDAO com @Inject.
@WebServlet(value="/adicionar")
public class AdicionaTarefa extends HttpServlet {

    @Inject
 private TarefaDAO dao;

 public void service(HttpServletRequest req, HttpServletResponse res) {

  Tarefa tarefa = new Tarefa();
  tarefa.setTitulo(req.getParameter("titulo"));
  tarefa.setDescricao(req.getParameter("descricao"));
  dao.adiciona(tarefa);
  
 }
}


Concluindo:


O CDI é uma ferramenta muito poderosa. Sem dúvida uma grande evolução desde a injeção tanto com Spring, com Guice e com Pico. Outro dia, irei elaborar um artigo sobre injeção de dependências com Spring. Espero que este artigo lhe seja proveitoso e sinta-se a vontade para perguntar nos comentários.

Um abraços,
Até a próxima!













Nenhum comentário:

Postar um comentário