JSFで会話スコープを終わらせつつ自画面に遷移

若干はまったのでメモ。

あるBeanの会話スコープを終わらせるにはConversation#end()を呼ぶわけですが、このメソッドを呼んだからと言ってプロパティが初期化されたりはしません。当たり前と言えば当たり前ですが。

このことが若干問題になることがあります。
会話スコープを終わらせたリクエストの遷移先画面で、そのBeanを参照する時です。
スコープは抜けたもののプロパティはそのままなので、初期化をもらすと遷移先画面でゴミが見えてしまうのです。


サンプルで確かめてみます。

XHTML側。conversation.xhtmlというファイル名にします。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
>
<h:head>
    <meta charset="UTF-8" />
    <title>会話スコープ</title>
</h:head>
<h:body>
<h:form>
    <ul>
        <li><h:commandButton action="#{conversationAction.begin}" value="会話スコープの開始"/></li>
        <li><h:commandButton action="#{conversationAction.countUp}" value="カウントアップ"/></li>
        <li><h:commandButton action="#{conversationAction.end}" value="会話スコープの終了"/></li>
    </ul>
    <hr/>
    #{conversationAction.counter}<br/>
    会話中: #{!conversationAction.transient}<br/>
</h:form>
</h:body>
</html>

Java側。

package sandbox.action;

import java.io.Serializable;
import java.util.concurrent.TimeUnit;

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

@Named
@ConversationScoped
public class ConversationAction implements Serializable {
    private static final long serialVersionUID = 8562666832318519767L;

    private int               counter;

    @Inject
    private Conversation      conversation;

    public void begin() {
        if (this.conversation.isTransient()) {
            this.conversation.begin();
            this.conversation.setTimeout(TimeUnit.SECONDS.toMicros(30));
        }
    }

    public void countUp() {
        this.counter++;
    }

    public String end() {
        if (!this.conversation.isTransient()) {
            this.conversation.end();
        }
        return null;
    }

    public int getCounter() {
        return this.counter;
    }

    public boolean isTransient() {
        return this.conversation.isTransient();
    }
}

初期画面はこんな感じです。

会話スコープを開始させてから、何回か「カウントアップ」を押します。

会話スコープを終了させます。期待する画面はカウンターが0に戻り、「会話中」が「false」となることです。
しかし・・・

ご覧のとおり、会話中ではないのにカウンターが0に戻っていません。

リダイレクトによる対処

会話スコープを終了するときにプロパティを初期化するのが直球な対処です。でもプロパティが増えると面倒だし、それにスコープを終わらせて役目が終わったはずのBeanを画面編集に利用するのはなんだか気持ちが悪いです。

そこで会話スコープを終了させて初期画面を表示するときに、リダイレクトを使います。

リダイレクトならば会話スコープの終了と初期画面表示が異なるリクエストで行われるので、初期画面が表示されるときはプロパティは初期化されています。

リダイレクトするには前述のメソッドのend()メソッドの戻り値を変更します。

    public String end() {
        if (!this.conversation.isTransient()) {
            this.conversation.end();
        }
        return "conversation?faces-redirect=true";
    }

もう一度同じ「会話スコープ開始」→「カウントアップ」→「会話スコープ終了」と操作すると

カウンターが0に戻ることが確認できます。

リダイレクトによる対処の欠点

リダイレクトによる対処はお手軽ではありますが、XHTMLファイルの名前を戻り値に含めないといけないのがちょっとした欠点です。
気軽にXHTMLファイル名を変えられなくなるので。