Capítulo 16

Apêndice - Integrando VRaptor e Spring

16.1 - Como fazer a integração?

Para usar os componentes do Spring no VRaptor basta configurá-los no applicationContext.xml da maneira que você já está acostumado a fazer quando trabalha com o Spring. Todos os componentes declarados no applicationContext.xml estarão disponíveis para os componentes do VRaptor, e vice-versa.

Um detalhe da integração é que se você usar algum listener do spring para carregar o applicationContext.xml (como o ContextLoaderListener), o VRaptor só consegue incluir os seus componentes após um reload do contexto, então se você precisar de um componente do VRaptor dentro de um do Spring, o do VRaptor precisa ser marcado como opcional:

@Autowired(optional=true)
public void setComponenteDoVRaptor(ComponenteDoVRaptor c) {...}

Se não houver o listener do Spring, e o applicationContext.xml estiver no classpath, o optional não é necessário.

16.2 - Integrando o Transaction Manager do Spring

Na nossa aplicação estamos controlando as transações manualmente, abrindo e fechando-as dentro de alguns métodos do DAO. Mas isso não é bom, principalmente se você precisar fazer mais de uma operação dentro da mesma transação.

O Spring possui um componente que controla transações, e podemos usá-lo para transferir esse controle para o Spring. Esse componente depende indiretamente de uma SessionFactory, que no momento é gerenciada pelo VRaptor.

Para passar a SessionFactory como dependência para o componente de transações do Spring, não podemos mais usar o CriadorDeSessionFactory, precisamos criar a SessionFactory do jeito do Spring, por exemplo com o componente AnnotationSessionFactoryBean.

Ao usar o SessionFactory do Spring, não podemos mais criar Sessions do jeito que fazíamos antes, apenas chamando o openSession, pois o Spring gerencia as Sessions de uma maneira que está acoplada a todos os componentes que precisam usá-la. Por causa disso precisaremos adaptar o nosso CriadorDeSession.

Nos exercícios abaixo mostraremos como integrar esse componente de transações como a nossa aplicação.

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de ensino que favorece o aprendizado com a qualidade reconhecida da Caelum. Você pode escolher um curso nas áreas de Java, Front-end, Ruby, Web, Mobile, .NET, PHP e outros, com um plano que dá acesso a todos os cursos.

Conheça os cursos online Alura.

16.3 - Exercícios: Transaction Manager

  1. Crie o arquivo applicationContext.xml dentro da pasta src:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xsi:schemaLocation="
           http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    
    </beans>
    
  2. Inclua o componente que gerencia as transações, baseado em anotações do Spring.

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xmlns:tx="http://www.springframework.org/schema/tx"
      xsi:schemaLocation="
           http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/tx 
           http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">
    
      <tx:annotation-driven />
    
    </beans>
    
  3. Esse componente de transações é genérico: funciona tanto com a JTA direto, com Hibernate/JPA, com JDBC, etc. Então para especificar qual dos gerenciadores de transação vai ser utilizado, você precisa adicionar um bean na configuração. No nosso caso, vamos usar transações do Hibernate:

    <beans xmlns...>
    
      <tx:annotation-driven />
      <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
      </bean>
    
    </beans>
    
  4. O transactionManager precisa de uma SessionFactory configurada. Já temos uma no VRaptor, mas ela não poderá ser usada. Remova a anotação @Component do CriadorDeSessionFactory, e adicione o bean que cria uma SessionFactory no Spring:

    <beans xmlns....>
      <!--...-->
      <bean id="sessionFactory" 
        class="org.springframework.orm.hibernate3.
          annotation.AnnotationSessionFactoryBean">
        <property name="configLocation">
          <value>classpath:/hibernate.cfg.xml</value>
        </property>
      </bean>
    </beans>
    
  5. Ao usar a SessionFactory do Spring estamos presos ao seu controle de Sessions. Isso significa que não podemos fazer o sessionFactory.openSession para obter Sessions. O Spring controla isso para você, então você é obrigado a usar componentes do Spring para conseguir uma Session, para poder usar a mesma que está participando da transação. Mas ao invés de mudar todo o nosso sistema para usar as sessions do Spring, vamos modificar o nosso CriadorDeSession para usar-las.

    Por causa do jeito que o Spring trabalha, temos que pedir uma Session para ele no momento que vamos usá-la, e não na criação do DAO, por exemplo. Então vamos usar um Proxy Dinâmico para poder passar uma Session no construtor do DAO, mas mesmo assim só pedir a Session pro Spring na hora de chamar algum método.

    Modifique o CriadorDeSession.

    import net.vidageek.mirror.dsl.Mirror;
    
    import org.springframework.orm.hibernate3.SessionFactoryUtils;
    
    import br.com.caelum.vraptor.proxy.MethodInvocation;
    import br.com.caelum.vraptor.proxy.Proxifier;
    import br.com.caelum.vraptor.proxy.SuperMethod;
    
    @Component
    public class CriadorDeSession implements ComponentFactory<Session> {
    
      private final SessionFactory factory;
      private final Proxifier proxifier;
      private Session session;
    
      public CriadorDeSession(SessionFactory factory, Proxifier proxifier) {
        this.factory = factory;
        this.proxifier = proxifier;
      }
    
      @PostConstruct
      public void abre() {
        this.session = proxifier.proxify(Session.class, 
          new MethodInvocation<Session>() {
            public Object intercept(Session proxy, 
                Method method, Object[] args, 
                SuperMethod superMethod) {
              Session sessionDoSpring = 
                  SessionFactoryUtils
                      .doGetSession(factory, true);
              return new Mirror().on(sessionDoSpring)
                  .invoke().method(method).withArgs(args);
            }
          });
      }
      public Session getInstance() {
        return this.session;
      }
      @PreDestroy
      public void fecha() {
        this.session.close();
      }
    
    }
    
  6. Para usar o controle de transações do Spring, remova a abertura e o fechamento de transações dos métodos do DAO, e anote o método com @Transactional. Repare que nem todos os métodos precisam de transações, só os que modificam o banco.

    import org.springframework.transaction.annotation.Transactional;
    
    @Component
    public class ProdutoDao {
    
      @Transactional
      public void salva(Produto produto) {
        //Transaction tx = session.beginTransaction();
        session.save(produto);
        //tx.commit();
      }
    
      public Produto carrega(Long id) {
        return (Produto) session.load(Produto.class, id);
      }
    
      @Transactional
      public void atualiza(Produto produto) {
        session.update(produto);
      }
    
      public List<Produto> listaTudo() {
        return this.session.createCriteria(Produto.class).list();
      }
    
      @Transactional
      public void remove(Produto produto) {
        session.delete(produto);
      }
    
      public List<Produto> busca(String nome) {
        return session.createCriteria(Produto.class)
          .add(Restrictions.ilike("nome", nome, MatchMode.ANYWHERE))
          .list();
      }
    
      public void recarrega(Produto produto) {
        session.refresh(produto);
      }
    
    }
    
  7. O Spring transactional usa o Spring AOP para gerenciar as transações. O Spring AOP tem uma restrição forte: para poder decorar suas classes ele usa proxies da Cglib. Esses proxies precisam que sua classe tenha um construtor sem argumentos, ou que as dependências da sua classe sejam sempre interfaces. E como programar voltado a interfaces é uma boa prática, pois diminui o acoplamento de uma classe com as suas dependências, vamos optar pela segunda solução.

    Renomeie a classe ProdutoDao para HibernateProdutoDao. Use o Ctrl+Shift+R no nome da classe.

    Extraia uma interface chamada ProdutoDao, selecionando todos os métodos. Com o cursor no nome da classe digite Ctrl+3 e então Extract Interface. Ou Alt+Shift+T e selecione Extract Interface. Marque o checkbox: "Use supertype where possible"