VRaptor

Utilizando o @ConversationScoped no VRaptor4

O VRaptor em sua versão 4 utiliza o CDI 1.1 como base. E uma das considerações a serem feitas quando usamos o CDI é o escopo de vida dos objetos. Sempre é preciso escolher com cuidado o escopo, levando em consideração qual vai ser a aplicação do objeto. Utilizar o @RequestScoped onde é necessário um @ApplicationScoped pode ser desastroso. Um escopo muito util nesse cenário é o @ConversationScoped que nos permite um controle maior conforme a nossa necessidade. A aplicação apresentada nesse tutorial está disponivel no GitHub: ScoreCDI.

Abaixo nosso Controller:

package br.com.angeliski.controller;

import java.io.Serializable;
import java.util.Random;

import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.inject.Inject;

import br.com.caelum.vraptor.Controller;
import br.com.caelum.vraptor.Get;
import br.com.caelum.vraptor.Post;
import br.com.caelum.vraptor.Result;

@Controller
@ConversationScoped
public class HomeController implements Serializable {

    private static final long serialVersionUID = 943045823176068998L;
    private Result result;
    private Conversation conversation;
    private Integer resultado;
    private int total;

    /**
     * @deprecated CDI eyes only
     */
    protected HomeController() {
        this(null, null);
    }

    @Inject
    public HomeController(Result result, Conversation conversation) {
        this.result = result;
        this.conversation = conversation;
    }

    @Get
    public void index() {
    }

    @Get
    public void game() {
        if (conversation.isTransient()) {
            conversation.begin();
        }
        result.include("cid", conversation.getId());
        gerarPergunta();
    }

    private void gerarPergunta() {
        Random random = new Random();
        Integer primeiroValor = random.nextInt(100);
        Integer segundoValor = random.nextInt(100);

        resultado = primeiroValor * segundoValor;

        result.include("primeiro", primeiroValor);
        result.include("segundo", segundoValor);
    }

    @Post
    public void game(Integer resposta) {
        if (resultado.equals(resposta)) {
            total++;
        }
        gerarPergunta();
        result.include("cid", conversation.getId());
    }

    @Get
    public void fim() {
        if (!conversation.isTransient()) {
            conversation.end();
        }
        result.include("total", total);
    }

}

A primeira distinção desse Controller é a anotação @ConversationScoped na classe. Isso indica para o CDI qual vai ser o escopo, mas cuidado: só isso não é suficiente para que a sua classe tenha um escopo maior que o escopo request. Para que o escopo se torne persistente, não perdendo os dados a cada requisição, é necessário injetar um objeto Conversation e chamar seu método begin. Isso pode ser observado aqui:

    @Get
    public void game() {
        if (conversation.isTransient()) {
            conversation.begin();
        }
        result.include("cid", conversation.getId());
        gerarPergunta();
    }

Nesse método a conversação é iniciada. O Conversation tem um estado padrão conhecido como transient. Quando o método begin é acionado ele passa para o long-running mantendo os dados até que seja invocado o método end do Conversation. Só que um detalhe importante deve ser observado. O CDI não tem como saber a qual objeto você está tratando. O único modo dele saber isso é você informando qual o identificador daquela conversação e isso é feito através do pârametro cid. É necessário informar na url esse pârametro para que o CDI consiga recuperar o objeto correto. Observe que o pârametro cid é incluido no result para que seja possível utilizar ele na pagina. O nome utilizado para incluir o id do Conversation no result não é fundamental. Ele poderia ser qualquer um, desde que fosse recuperado corretamente na pagina. Só é fundamental utilizar o pârametro cid quando a requisição for enviada para o servidor. Observe como ficou a nossa pagina que vai utilizar esse parâmetro.

<\%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"\%>
<\%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"\%>

<!DOCTYPE html>
<html>
<head>
<title>Score CDI</title>

</head>
<body>
    <h4>Sessão atual ${cid}</h4>
    <p>Quanto é ${primeiro} * ${segundo}?</p>
    <form action="${linkTo[HomeController].game}?cid=${cid}" method="POST">
        <input name="resposta" type="number" required>
        <button type="submit">Continuar jogo!</button>
    </form>
    
    <a href="${linkTo[HomeController].fim}?cid=${cid}">Finalizar!</a>

</body>
</html>

Você pode observar que a action do formulário faz o uso correto do cid. Quando essa requisição for enviada ao servidor, vai enviar junto o identificador correto. Enquanto a requisição for feita enviando o cid o número exibido em Sessão atual vai continuar o mesmo, pois a conversação vai se manter. Você pode fazer um teste acessando diretamente ‘/home/game’ sem enviar o cid no pârametro. Vai ser iniciada uma nova conversação e o cid irá se modificar.

Por fim temos o método que encerra o long-running através do método end:

    @Get
    public void fim() {
        if (!conversation.isTransient()) {
            conversation.end();
        }
        result.include("total", total);
    }

Aqui é valido observar que caso você não encerre uma conversação, ela não vai se manter em memória por muito mais que dois minutos, que é o tempo padrão. Isso garante que diferente do @SessionScoped os recursos vão ser liberados antes, caso a aplicação não encerre o estado daquele objeto. O timeout pode ser ajustado através do método setTimeout conforme a necessidade, mas normalmente não é preciso.