JSF (mojarra) ステートレスビューを試してみる

JSF2.2のビッグチケットとして盛り込まれたステートレスビューですが、HttpSessionへのアクセス量を減らせるメリットの一方、制約事項もあります。Wildfly-9.0.0.CR2(mojarra-2.2.11)で色々と試してみたので以下にまとめます。

ステートレスビューの使い方

<f:view transient="true">で囲うだけで、対象のビューのフォーム値などの状態をセッションに保持しなくなります。

<html xmlns="http://www.w3.org/1999/xhtml" 
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:h="http://java.sun.com/jsf/html">
  <f:view transient="true">
    <h:head>
      <title>JSF Hello World</title>
    </h:head>
    <h:body>
      <h1>JSF Hello World</h1>
      <h:form>
       ...
      </h:form>
    </h:body>
  </f:view>
</html>

いつから使える?

仕様としてはJSF2.2の新機能としてJAVASERVERFACES_SPEC_PUBLIC-1055として作成されましたが、JSFのRIであるmojarraではJAVASERVERFACES-2731の修正により、Java EE6世代の2.1系である2.1.19から使えるようになっています。

ステートレスビューのメリット

一般的には以下のようなメリットが得られると言われています。

  • レスポンスタイム向上
  • スケール性の向上・ヒープ使用量の低減

手元のHelloWorldアプリでは違いがよくわかなかったですが、スループット・レスポンスタイム向上度については、アプリケーションにも依存すると思うので、既存のアプリに<f:view transient="true">を入れて測ってみるのがおすすめです。

mojarra-2.2.11の場合、シリアライズされたビュー状態をcom.sun.faces.renderkit.ServerSideStateHelper.LogicalViewMapという属性名でセッションに登録しているようですが、ステートレスビューにするとLogicalViewMapへの登録がなくなりました。あくまで仮説ですが、大規模にセッションレプリケーションしている環境では、HttpSessionへのアクセスが減ることでシステム全体のスループットにも好影響があるのかと思います。

ステートレスビューのデメリット

一方で、以下のようなデメリットもあります。

  • ViewScopeが使えない
  • CSRF対策が必要
ViewScopeが使えない

JSFAjax機能を使う時によく使う、同一ビューへのリクエストであれば値を保持し続けるViewScopeが使えません。Primefacesなどの内部的にAjaxが使われているコンポーネント利用時にも注意が必要と思います。ステートレスビューとViewScopeを組み合わせると、アクセス時に以下のような警告ログが出力され、ViewScopeが設定されていてもRequestScopeと同様に振る舞います。

2015-06-21 20:27:23,737 WARNING [com.sun.faces.application.view.ViewScopeManager] (default task-2) @ViewScoped beans are not supported on stateless views

JSF2.2においてCDIでもViewScopeを定義できるアノテーションが導入されていますが、いずれの場合でもステートレスビューと組み合わせることはできません。

  • javax.faces.bean.ViewScoped (JSF2.0導入, JSFの@ManagedBeanと使う)
  • javax.faces.view.ViewScoped (JSF2.2導入, CDIの@Namedと使う)
CSRF対策が必要

今までのステートフルビューでは、hiddenフィールドのjavax.faces.ViewStateが暗黙的なCSRF対策トークンとして機能しており、この正しいID値をPOSTしないとビューが復元できず、業務ロジックが実行される前のRESTORE VIEWフェーズで例外となっていました。

<input type="hidden" name="javax.faces.ViewState" id="j_id1:javax.faces.ViewState:0" value="1867897074285420267:8829522400179303592" autocomplete="off" />

ステートレスビューでは、ViewStateに以下のように固定的な文字列statelessが割り当てられる為、CSRF対策トークンとして機能しません。

<input type="hidden" name="javax.faces.ViewState" id="j_id1:javax.faces.ViewState:0" value="stateless" autocomplete="off">

JSF2.2から盛り込まれた明示的なCSRF対策トークン付与機能によって以下のように設定することもできますが、CSRF対策したい部分(購入完了、決済完了などDB登録処理)のビューを個別に指定する手間は掛かります。JSFのフォームPOST先のURLは、現在表示しているViewであることを意識しながら設定するのはそれなりに大変です。

WEB-INF/faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
              version="2.2">
    <protected-views>
        <url-pattern>/inputform.xhtml</url-pattern>
    </protected-views>
</faces-config>

この設定を加えてinputform.xhtmlにフォワードさせると、以下のようにformのaction属性の値にトークンが付与されます。このトークンの値がない/間違ってる状態でHTTP POSTされると、javax.faces.application.ProtectedViewExceptionがスローされ、RESTORE VIEWフェーズの途中で処理が終了します。

<form id="j_idt7" name="j_idt7" method="post" action="/jsftest/input.xhtml?javax.faces.Token=i0hAEnknIkohc0GnhQ%3D%3D" enctype="application/x-www-form-urlencoded">

まとめ

  • mojarra2.1.19(EE6系)および2.2.0-m10(EE7系)より、ステートレスビューが利用可
  • JSFステートレスビューには制約事項もある
    • ViewScopeが使えないので、Ajax機能が使いにくい
    • javax.faces.ViewStateによるCSRF対策ができないため、別の対策が必要
  • 制約事項を考えると、特に困ってないなら無理にステートレスビューを使う必要はないかと思う。遅いビューに部分的に使いたい。