前書き
ViewExpiredException
必ずスローされるjavax.faces.STATE_SAVING_METHOD
に設定されているserver
(デフォルト)とエンドユーザーが経由ビュー上のHTTP POSTリクエストを送信する<h:form>
と<h:commandLink>
、<h:commandButton>
または<f:ajax>
関連付けられたビューステートはもうセッションでは使用できませんが、。
ビューステートは、非表示の入力フィールドの値として識別されるjavax.faces.ViewState
の<h:form>
。状態保存メソッドをに設定するとserver
、これには、セッションでシリアル化されたビューステートを参照するビューステートIDのみが含まれます。したがって、何らかの理由でセッションが期限切れになった場合(サーバー側またはクライアント側でタイムアウトになったか、ブラウザで何らかの理由HttpSession#invalidate()
により、またはサーバーを呼び出すことによって、またはセッションCookieのサーバー固有のバグにより、セッションCookieが維持されなくなった場合)WildFlyで知られている)、シリアル化されたビューステートはセッションで使用できなくなり、エンドユーザーはこの例外を受け取ります。セッションの動作を理解するには、「サーブレットはどのように動作するか」も参照してください。インスタンス化、セッション、シェア変数、マルチスレッド。
JSFがセッションに保存するビューの量にも制限があります。制限に達すると、最も長く使用されていないビューが期限切れになります。com.sun.faces.numberOfViewsInSessionとcom.sun.faces.numberOfLogicalViewsも参照してください。
状態保存メソッドがに設定されているclient
場合、javax.faces.ViewState
非表示の入力フィールドにはシリアル化されたビュー状態全体が含まれるためViewExpiredException
、セッションが期限切れになってもエンドユーザーはを取得しません。ただし、クラスター環境(「エラー:MACは検証されませんでした」が症状です)で、および/または構成されたクライアント側の状態に実装固有のタイムアウトがある場合、および/またはサーバーが再起動中にAESキーを再生成する場合に発生する可能性があります、状態保存メソッドがクライアントに設定され、ユーザーセッションがそれを解決する方法として有効である場合のクラスター環境でのViewExpiredExceptionの取得も参照してください。
解決策に関係なく、を使用しないでくださいenableRestoreView11Compatibility
。元のビューステートはまったく復元されません。これは基本的に、ビューとそれに関連するすべてのビュースコープBeanを最初から再作成するため、元のデータ(状態)がすべて失われます。アプリケーションが混乱するように動作するため(「ねえ、私の入力値はどこにあるの?。??」)、これはユーザーエクスペリエンスにとって非常に悪いことです。ステートレスビューを使用するか、<o:enableRestorableView>
、すべてのビューではなく特定のビューでのみ管理できるようにしてください。
JSFがビューステートを保存する必要がある理由については、次の回答に進んでください。なぜJSFはサーバー上のUIコンポーネントの状態を保存するのですか?
ページナビゲーションでのViewExpiredExceptionの回避
避けるために、ViewExpiredException
状態保存に設定され、ログアウト後に例えばナビゲーション戻ったときにserver
のみ、ログアウト後のPOSTリクエストをリダイレクトするには、十分ではありません。また、しないようにブラウザに指示する必要があります、動的JSFページをキャッシュます。そうしないと、ブラウザにGETリクエストを送信するときに(たとえば、[戻る]ボタンによって)、サーバーから新しいページをリクエストするのではなく、キャッシュからページが表示される場合があります。
javax.faces.ViewState
キャッシュされたページの非表示フィールドには、現在のセッションでは無効なビューステートID値が含まれている可能性があります。ページ間ナビゲーションにGET(通常のリンク/ボタン)ではなくPOST(コマンドリンク/ボタン)を(ab)使用していて、キャッシュされたページでそのようなコマンドリンク/ボタンをクリックすると、これが順番に行われます。で失敗するViewExpiredException
。
JSF 2.0でログアウト後にリダイレクトを起動<redirect />
するには<navigation-case>
、問題がある場合はに追加するか?faces-redirect=true
、outcome
値に追加します。
<h:commandButton value="Logout" action="logout?faces-redirect=true" />
または
public String logout() {
// ...
return "index?faces-redirect=true";
}
動的JSFページをキャッシュしないようにブラウザーに指示するFilter
には、のサーブレット名にマップされるを作成しFacesServlet
、必要な応答ヘッダーを追加してブラウザーのキャッシュを無効にします。例えば
@WebFilter(servletNames={"Faces Servlet"}) // Must match <servlet-name> of your FacesServlet.
public class NoCacheFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (!req.getRequestURI().startsWith(req.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER)) { // Skip JSF resources (CSS/JS/Images/etc)
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
res.setHeader("Pragma", "no-cache"); // HTTP 1.0.
res.setDateHeader("Expires", 0); // Proxies.
}
chain.doFilter(request, response);
}
// ...
}
ページの更新時にViewExpiredExceptionを回避する
ViewExpiredException
状態保存がに設定されているときに現在のページを更新するときに回避するためserver
、GET(通常のリンク/ボタン)だけでページ間ナビゲーションを実行していることを確認するだけでなく、次のことも確認する必要があります。フォームの送信にajaxのみを使用していること。とにかくフォームを同期的に(ajax以外で)送信する場合は、ビューをステートレスにする(後のセクションを参照)か、POSTの後にリダイレクトを送信する(前のセクションを参照)のが最善です。
持つViewExpiredException
ページでリフレッシュすると、デフォルトの設定では非常にまれなケースです。これは、JSFがセッションに保存するビューの数の制限に達した場合にのみ発生します。そのため、制限を手動で非常に低く設定した場合、または「バックグラウンド」で新しいビューを継続的に作成している場合にのみ発生します(たとえば、同じページに不適切に実装されたajaxポーリングまたは不適切に実装された404同じページの壊れた画像のエラーページ)。この制限の詳細については、com.sun.faces.numberOfViewsInSessionとcom.sun.faces.numberOfLogicalViewsも参照してください。もう1つの原因は、ランタイムクラスパスで重複するJSFライブラリが競合していることです。JSFをインストールする正しい手順は、JSF wikiページに概説されています。
ViewExpiredExceptionの処理
あなたが避けられないを処理したい場合はViewExpiredException
、任意のページのPOSTアクションが、すでにいくつかのブラウザタブ/ウィンドウで開いた後、あなたが別のタブ/ウィンドウにログアウトしている間、あなたが指定したいerror-page
そのためにweb.xml
行くどの「セッションがタイムアウトしました」ページに移動します。例えば
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/WEB-INF/errorpages/expired.xhtml</location>
</error-page>
実際にさらにホームページまたはログインページにリダイレクトする場合は、必要に応じてエラーページのメタリフレッシュヘッダーを使用します。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Session expired</title>
<meta http-equiv="refresh" content="0;url=#{request.contextPath}/login.xhtml" />
</head>
<body>
<h1>Session expired</h1>
<h3>You will be redirected to login page</h3>
<p><a href="#{request.contextPath}/login.xhtml">Click here if redirect didn't work or when you're impatient</a>.</p>
</body>
</html>
(0
in content
はリダイレクトまでの秒数を表し0
ます。つまり、「すぐにリダイレクトする」という意味です。たとえば、リダイレクトを使用3
してブラウザーに3秒待機させることができます)
ajaxリクエスト中の例外の処理には特別なが必要であることに注意してくださいExceptionHandler
。JSF / PrimeFaces ajaxリクエストでのセッションタイムアウトとViewExpiredException処理も参照してください。OmniFaces FullAjaxExceptionHandler
ショーケースページでライブサンプルを見つけることができます(これは非ajaxリクエストもカバーしています)。
また、あなたの「一般的な」エラーページが上にマップされなければならないことに注意<error-code>
する500
のではなく、<exception-type>
例えばのjava.lang.Exception
かjava.lang.Throwable
、そうでない場合に包まれたすべての例外ServletException
などはViewExpiredException
、まだ一般的なエラーページに終わるでしょう。web.xmlのjava.lang.Throwable error-pageに示されているViewExpiredExceptionも参照してください。
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/errorpages/general.xhtml</location>
</error-page>
ステートレスビュー
まったく異なる代替手段は、JSFビューをステートレスモードで実行することです。この方法では、JSF状態は何も保存されず、ビューが期限切れになることはありませんが、リクエストごとにゼロから再構築されます。ステートレスビューをオンにするには、transient
属性を<f:view>
toにtrue
。
<f:view transient="true">
</f:view>
このように javax.faces.ViewState
隠しフィールドは"stateless"
Mojarraで固定値を取得します(この時点ではMyFacesをチェックしていません)。この機能はMojarra 2.1.19および2.2.0で導入され、古いバージョンでは使用できないことに注意してください。
その結果、ビュースコープのBeanを使用できなくなります。これで、リクエストスコープBeanのように動作します。欠点の1つは、非表示の入力や緩い要求パラメーターをいじって、自分で状態を追跡する必要があることです。主に、ajaxイベントによって制御されるrendered
、readonly
またはdisabled
属性を持つ入力フィールドを持つフォームが影響を受けます。
<f:view>
は必ずしもビュー全体で一意である必要はなく、マスターテンプレートにのみ存在する必要もないことに注意してください。テンプレートクライアントに再宣言してネストすることも完全に合法です。基本的には親を「拡張」します<f:view>
ます。例:マスターテンプレート:
<f:view contentType="text/html">
<ui:insert name="content" />
</f:view>
そしてテンプレートクライアントで:
<ui:define name="content">
<f:view transient="true">
<h:form>...</h:form>
</f:view>
</f:view>
でラップし<f:view>
て、<c:if>
条件付きにすることもできます。に適用されることに注意してください<h:form>
上記の例のように、ネストされたコンテンツだけでなく、ビュー全体に。
こちらもご覧ください
具体的な問題とは無関係に、純粋なページ間ナビゲーションにHTTP POSTを使用することは、ユーザー/ SEOにあまり適していません。JSF 2.0では、あなたは本当に好むべきです<h:link>
、単純なバニラのページ間ナビゲーション<h:button>
よりも、か、それより<h:commandXxx>
ます。
だから例えばの代わりに
<h:form id="menu">
<h:commandLink value="Foo" action="foo?faces-redirect=true" />
<h:commandLink value="Bar" action="bar?faces-redirect=true" />
<h:commandLink value="Baz" action="baz?faces-redirect=true" />
</h:form>
より良い
<h:link value="Foo" outcome="foo" />
<h:link value="Bar" outcome="bar" />
<h:link value="Baz" outcome="baz" />
こちらもご覧ください