Capítulo 9

Refatorando os DAOs

9.1 - Injeção de dependências no DAO

Existe algo errado com o ProdutoDao: ele está criando uma Session, que é uma dependência dele. Usando injeção de dependências, podemos receber a Session no construtor, e o VRaptor vai se encarregar de criar essa Session e passar pro construtor do dao.

@Component
public class ProdutoDao {

  private final Session session;

  public ProdutoDao(Session session) {
    this.session = session;
  }
  //...
}

Mas como falar que a Session pode ser usada como dependência?

Poderíamos fazer a mesma alteração que fizemos no ProdutoDao, anotando a classe Session do pacote org.hibernate com @Component. Assim, a sessão do Hibernate também seria gerenciada pelo VRaptor. Mas será que podemos anotar uma classe do Hibernate? Só se baixássemos o código fonte e compilássemos na mão, junto com nosso projeto. Daria muito trabalho e seria muito estranho, nosso projeto alterando outro projeto.

Para resolver isso, o VRaptor possui um mecanismo que nos auxilia a passar dependências de outros projetos para nossas classes. Basta criarmos uma classe que implementa a interface ComponentFactory. Essa interface define um único método, o getInstance.

Criando essa classe, podemos definir a lógica de criação de um determinado objetos, e o VRaptor usará a instância retornada para passar como dependência para outras classes. No nosso caso, podemos criar uma classe que cria sessões do Hibernate.

Já temos uma classe que cria uma classe que nos dá uma instância de Session: a CriadorDeSession, então para transformá-la em um ComponentFactory basta implementar a interface. Mas o método que nos dá a Session se chama getSession, e a interface espera que o método se chame getInstance. E, por último, anotamos a classe com @Component para que o VRaptor saiba como criá-la. A classe ficará assim:

import br.com.caelum.vraptor.ioc.ComponentFactory;

@Component
public class CriadorDeSession implements ComponentFactory<Session> {

  public Session getInstance() {
    AnnotationConfiguration configuration = 
        new AnnotationConfiguration();
    configuration.configure();

    SessionFactory factory = configuration
        .buildSessionFactory();
    Session session = factory.openSession();
    return session;
  }

}

Note que utilizamos Generics para informar qual será o tipo de objeto criado por essa classe.

O método getInstance ainda está fazendo coisas demais: ele cria uma AnnotationConfiguration, configura, constrói uma SessionFactory, e só então abre uma sessão. Não seria suficiente só abrir uma sessão a partir da SessionFactory? A SessionFactory é uma dependência para criar uma Session. Então vamos recebê-la no construtor da classe.

@Component
public class CriadorDeSession implements ComponentFactory<Session> {
  
  private final SessionFactory factory;
  
  public CriadorDeSession(SessionFactory factory) {
    this.factory = factory;
  }
  
  public Session getInstance() {
    return factory.openSession();
  }
}

Podemos usar a mesma idéia e criar um ComponentFactory para SessionFactory, com o código que removemos da CriadorDeSession. Não podemos nos esquecer da anotação @Component:

@Component
public class CriadorDeSessionFactory implements 
    ComponentFactory<SessionFactory> {
  
  public SessionFactory getInstance() {
    AnnotationConfiguration configuration = 
        new AnnotationConfiguration();
    configuration.configure();

    SessionFactory factory = configuration
        .buildSessionFactory();
    return factory;
  }

}

Poderíamos continuar esse processo, e criar uma ComponentFactory para AnnotationConfiguration também, mas vamos parar por aqui. Resumindo o que acabamos de fazer, chegamos a seguinte situação: ProdutosController depende de ProdutoDao que depende de Session que depende de SessionFactory.

À primeira vista pode parecer que fizemos muita coisa só pra resolver o problema da Session. Mas a vantagem de utilizarmos essa abordagem é que para as nossas próximas lógicas, não precisaremos fazer nada. Se tivermos que criar 50 DAOs no nosso sistema, não precisamos mais se preocupar com Sessions, basta receber uma no construtor.

9.2 - Exercícios

  1. Faça a classe ProdutoDao receber uma Session no construtor. Remova o método getSession:

    @Component
    public class ProdutoDao {
    
      private final Session session;
    
      public ProdutoDao(Session session) {
        this.session = session;
      }
      //...
    }
    
  2. Modifique a classe CriadorDeSession. Faça com que essa classe implemente a interface ComponentFactory, passando a Session como tipo genérico. Renomeie o método getSession para getInstance, e remova o static.

    import br.com.caelum.vraptor.ioc.ComponentFactory;
    
    @Component
    public class CriadorDeSession implements ComponentFactory<Session> {
        public Session getInstance() {
          AnnotationConfiguration configuration = new AnnotationConfiguration();
          configuration.configure();
      
          SessionFactory factory = configuration.buildSessionFactory();
          Session session = factory.openSession();
          return session;
        }
    }
    
  3. A classe CriadorDeSession está fazendo coisas demais. Recorte a parte que cria uma SessionFactory e receba uma SessionFactory no construtor. A classe deve ficar assim:

     1 package br.com.caelum.goodbuy.infra;
     2 
     3 // import's
     4 
     5 public class CriadorDeSession implements ComponentFactory<Session> {
     6 
     7   public CriadorDeSession(SessionFactory factory) {
     8   }
     9 
    10   public Session getInstance() {
    11     Session session = factory.openSession();
    12     return session;
    13   }
    14 
    15 }
    
  4. Após ter criado o construtor, guarde o parâmetro factory em um atributo. Utilize as teclas de atalho do Eclipse. Pressione as teclas Ctrl + 1 (quick fix) no parâmetro factory, e o Eclipse nos mostrará algumas sugestões. Selecione a primeira opção, Assign parameter to new field.

    ioc-com-vraptor2.png
  5. Anote a classe com @Component, para indicar que essa classe é uma dependência e pode ser instanciada pelo VRaptor sempre que necessário.

  6. Crie a classe CriadorDeSessionFactory no pacote br.com.caelum.goodbuy.infra que implementa a interface ComponentFactory<SessionFactory>.

    1 package br.com.caelum.goodbuy.infra;
    2 
    3 // import's
    4 
    5 public class CriadorDeSessionFactory implements 
    6     ComponentFactory<SessionFactory> {
    7 
    8 }
    
  7. A classe não compila, pois falta implementar o método que foi definido na interface. Para isso utilize as teclas de atalho do Eclipse: coloque o cursor no nome da classe e digite Ctrl+1. Selecione a opção Add unimplemented methods.

    Coloque a criação da fábrica de sessões que você tinha recortado da outra classe nesse método.

     1 package br.com.caelum.goodbuy.infra;
     2 
     3 // import's
     4 
     5 public class CriadorDeSessionFactory 
     6     implements ComponentFactory<SessionFactory> {
     7 
     8   public SessionFactory getInstance() {
     9     AnnotationConfiguration configuration = 
    10         new AnnotationConfiguration();
    11     configuration.configure();
    12   
    13     SessionFactory factory = 
    14         configuration.buildSessionFactory();
    15     return factory;
    16   }
    17 
    18 }
    
  8. Anote a classe com @Component, para indicar que essa classe é uma dependência e pode ser instanciada pelo VRaptor sempre que necessário.

  9. As classes de teste que tínhamos criado não estão compilando mais, pois mudamos o construtor do ProdutoDao. Mude as chamadas, usando as ComponentFactories para criar as dependências. Note que na aplicação web, o VRaptor que vai se encarregar executar esse código.

    SessionFactory factory = new CriadorDeSessionFactory().getInstance();
    
    Session session = new CriadorDeSession(factory).getInstance();
    
    ProdutoDao dao = new ProdutoDao(session);
    
  10. Acesse o sistema e veja que ele continua funcionando como antes.

Tire suas dúvidas no GUJ Respostas

O GUJ é um dos principais fóruns brasileiros de computação e o maior em português sobre Java. A nova versão do GUJ é baseada em uma ferramenta de perguntas e respostas (QA) e tem uma comunidade muito forte. São mais de 150 mil usuários pra ajudar você a esclarecer suas dúvidas.

Faça sua pergunta.

9.3 - Analisando o código

Já conseguimos cadastrar um produto pelo browser e já criamos algumas classes para injeção de dependências para nossas próximas lógicas, mas será que nosso código atual está certo?

Toda vez que precisamos de uma sessão do Hibernate, o VRaptor injeta no dao uma sessão nova. Essa sessão é criada de uma fábrica de sessões, e essa fábrica também é criada. Ou seja, para cada requisição, estamos criando uma fábrica nova. Mas temos que nos lembrar que isso não deveria ser feito. É custoso ter que criar uma fábrica de sessões para cada requisição.

Uma alternativa para resolver esse problema é utilizar um bloco estático dentro do CriadorDeSessionFactory. Outra alternativa seria utilizar o Design Pattern Singleton, fazendo com que apenas uma instância de determinada classe seja criada.

Mas como estamos utilizando o VRaptor, vamos utilizar um recurso dele para esse controle.

9.4 - Escopos definidos pelo VRaptor

O VRaptor nos permite definir o tempo de vida de instâncias de determinada classe. Esse tempo de vida é importante porque cada componente tem uma responsabilidade, e alguns devem sobreviver por mais tempo que outros.

Alguns componentes devem ter o tempo de vida de uma requisição, ou seja, enquanto estamos atendendendo uma solicitação do cliente. Esse caso seria aplicado à sessão do Hibernate, por exemplo.

Mas alguns componentes devem durar mais que uma requisição, devem durar enquanto um cliente estiver logado por exemplo. Esse seria o caso de um carrinho de compras. Para cada produto que um cliente quiser comprar, esse produto deve ser adicionado no mesmo carrinho, e enquanto o cliente estiver usando o sistema o carrinho deve continuar guardando os produtos.

Para alguns casos, um componente deve durar para todas as requisições, e esse componente pode ser utilizado por todos os clientes. Esse caso poderia ser a fábrica de sessões do Hibernate. Essa fábrica ficaria única para toda a aplicação, e qualquer cliente que precisar de uma sessão, usaríamos essa fábrica.

Para cada cenário acima, o VRaptor definiu um tempo de vida. Esse tempo de vida no VRaptor é chamado de escopo. O VRaptor define quatro escopos, e para utilizá-los, basta anotarmos nossos componentes com uma das seguintes anotações:

Para nossa classe CriadorDeSessionFactory, queremos ter apenas uma instância dessa classe para toda nossa aplicação, então vamos anotá-la com @ApplicationScoped.

 1 package br.com.caelum.goodbuy.infra;
 2 
 3 // import's
 4 
 5 @Component
 6 @ApplicationScoped
 7 public class CriadorDeSessionFactory implements
 8     ComponentFactory<SessionFactory> {
 9 
10   public SessionFactory getInstance() {
11     AnnotationConfiguration configuration = 
12         new AnnotationConfiguration();
13     configuration.configure();
14     return configuration.buildSessionFactory();
15   }
16 
17 }

Agora o VRaptor vai criar apenas uma instância dessa classe. Mas repare que o método getInstance cria toda vez uma fábrica nova. Precisamos refatorar para que seja criada apenas uma fábrica.

Para resolver esse problema, poderíamos criar um atributo do tipo SessionFactory e colocar no construtor da classe a criação da fábrica.

@Component
@ApplicationScoped
public class CriadorDeSessionFactory implements 
    ComponentFactory<SessionFactory>{

  private final SessionFactory factory;

  public CriadorDeSessionFactory() {
    AnnotationConfiguration configuration = 
        new AnnotationConfiguration();
    configuration.configure();

    factory = configuration.buildSessionFactory();
  }

  public SessionFactory getInstance() {
    return factory;
  }

}

Mas o VRaptor permite a utilização de anotações bem úteis do Java EE 5: o @PostConstruct e o @PreDestroy. Essas anotações devem ser anotadas em métodos e têm a seguinte semântica:

Desse modo, podemos usar um método anotado com @PostConstruct para criar a fábrica de sessões, e como é uma boa prática fechar todos os recursos que abrimos, devemos criar um método anotado com @PreDestroy que vai fechar a fábrica.

Fazendo essas modificações, nossa classe ficaria assim:

@Component
@ApplicationScoped
public class CriadorDeSessionFactory implements 
    ComponentFactory<SessionFactory> {

  private SessionFactory factory;

  @PostConstruct
  public void abre() {
    AnnotationConfiguration configuration = 
        new AnnotationConfiguration();
    configuration.configure();

    this.factory = configuration.buildSessionFactory();;
  }

  public SessionFactory getInstance() {
    return this.factory;
  }

  @PreDestroy
  public void fecha() {
    this.factory.close();
  }
}

Note que não precisamos de um bloco estático ou um singleton, pois o VRaptor é que vai se encarregar de criar apenas uma instância da fábrica, e quando o recurso não for mais necessário, o VRaptor vai fechar a fábrica.

9.5 - Fechando a sessão do Hibernate

Outro problema que temos no nosso código são as sessões do Hibernate. Para cada requisição, abrimos uma sessão e utilizamos sempre que for necessário durante toda a requisição. Mas quando fechamos essa sessão? No nosso caso, nós não estamos fechando.

Mas onde colocaríamos o fechamento da sessão? No dao? Na lógica? Mas será que nossa lógica que deveria fechar? Não seria bom que o VRaptor fechasse para nós a sessão no final da requisição?

Utilizando as anotações que vimos na sessão anterior, @PostConstruct e @PreDestroy, conseguimos fazer um código muito elegante e de fácil entendimento.

Na classe CriadorDeSession, podemos criar dois métodos, um para abrir e outro para fechar a sessão. Dessa forma, não precisaremos nos preocupar com esse tipo de tarefa, que não faz parte da regra de negócio.

A classe ficaria assim:

 1 package br.com.caelum.goodbuy.infra;
 2 
 3 // import's
 4 
 5 @Component
 6 public class CriadorDeSession implements ComponentFactory<Session> {
 7 
 8   private final SessionFactory factory;
 9   private Session session;
10 
11   public CriadorDeSession(SessionFactory factory) {
12     this.factory = factory;
13   }
14 
15   @PostConstruct
16   public void abre() {
17     this.session = factory.openSession();
18   }
19   public Session getInstance() {
20     return this.session;
21   }
22   @PreDestroy
23   public void fecha() {
24     this.session.close();
25   }
26 
27 }

Note que criamos um atributo chamado session, que guardará uma sessão do Hibernate. Temos que lembrar que componentes que controlados pelo VRaptor sempre têm um escopo, mesmo não estando anotado. O escopo padrão para componentes é o de requisição, ou seja, daria na mesma anotar essa classe com @RequestScoped. Desse modo, uma sessão vai ser aberta no começo da requisição, e será fechada assim que acabar a requisição

Nova editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não conhecem programação para revisar os livros tecnicamente a fundo. Não têm anos de experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

9.6 - Exercícios

  1. Anote a classe CriadorDeSessionFactory com @ApplicationScoped.

     1 package br.com.caelum.goodbuy.infra;
     2 
     3 // import's
     4 
     5 @Component
     6 @ApplicationScoped
     7 public class CriadorDeSessionFactory implements
     8     ComponentFactory<SessionFactory> {
     9   //...
    10 }
    
  2. Refatore essa classe para que seja criado apenas uma instância da fábrica.

     1 package br.com.caelum.goodbuy.infra;
     2 
     3 // import's
     4 
     5 @Component
     6 @ApplicationScoped
     7 public class CriadorDeSessionFactory implements 
     8     ComponentFactory<SessionFactory>{
     9 
    10   private SessionFactory factory;
    11 
    12   @PostConstruct
    13   public void abre() {
    14     AnnotationConfiguration configuration = 
    15         new AnnotationConfiguration();
    16     configuration.configure();
    17 
    18     this.factory = configuration.buildSessionFactory();
    19   }
    20 
    21   public SessionFactory getInstance() {
    22     return this.factory;
    23   }
    24 
    25   @PreDestroy
    26   public void fecha() {
    27     this.factory.close();
    28   }
    29 }
    
  3. Refatore a classe CriadorDeSession, colocando os métodos de callback e colocando a sessão do Hibernate em um atributo.

    @Component
    public class CriadorDeSession implements ComponentFactory<Session> {
    
      private final SessionFactory factory;
      private Session session;
    
      public CriadorDeSession(SessionFactory factory) {
        this.factory = factory;
      }
    
      @PostConstruct
      public void abre() {
        this.session = factory.openSession();
      }
      
      public Session getInstance() {
        return this.session;
      }
      
      @PreDestroy
      public void fecha() {
        this.session.close();
      }
    
    }
    
  4. Reinicie o tomcat, acesse o sistema e veja que tudo continua funcionando

  5. (Opcional) Coloque System.out.println nos métodos dos ComponentFactories, para ver a ordem que os métodos são chamados

  6. (Opcional) Faça em uma folha de papel todo o fluxo de cadastro de um usuário. Esse fluxo pode ser feito de qualquer forma, com flechas e caixas de texto por exemplo. O ponto importante desse exercício é ficar claro como está a arquitetura do seu projeto.