序章
が(デフォルト)に設定され、エンドユーザーがビューでHTTP POSTリクエストを送信するViewExpiredException
たびに、がスローされますが、関連付けられたビューステートはセッションで使用できなくなります。javax.faces.STATE_SAVING_METHOD
server
<h:form>
<h:commandLink>
<h:commandButton>
<f:ajax>
ビューステートは、の非表示の入力フィールドjavax.faces.ViewState
の値として識別されます<h:form>
。状態保存方法をに設定するとserver
、これには、セッション内のシリアル化されたビュー状態を参照するビュー状態IDのみが含まれます。したがって、次のいずれかの理由でセッションが期限切れまたは不在の場合...
- セッションオブジェクトがサーバーでタイムアウトしました
- クライアントでセッションCookieがタイムアウトしました
- セッションCookieがクライアントで削除されます
HttpSession#invalidate()
サーバーで呼び出されます
SameSite=None
セッションCookieに欠落している(したがって、サードパーティのサイト(支払いなど)がコールバックURLを介してサイトに戻るときにChromeはそれらを送信しません)
...その後、シリアル化されたビューステートはセッションで使用できなくなり、エンドユーザーはこの例外を受け取ります。セッションの動作を理解するには、サーブレットの動作も参照してください。インスタンス化、セッション、共有変数、マルチスレッド。
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をインストールする正しい手順は、JSFwikiページに概説されています。
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
incontent
はリダイレクトまでの秒数を表します。つまり、0
「すぐにリダイレクトする」ことを意味します。たとえば3
、ブラウザにリダイレクトを3秒間待機させることができます)
ajaxリクエスト中に例外を処理するには、特別なが必要であることに注意してくださいExceptionHandler
。JSF /PrimeFacesajaxリクエストでのセッションタイムアウトとViewExpiredException処理も参照してください。OmniFacesのFullAjaxExceptionHandler
ショーケースページでライブの例を見つけることができます(これはajax以外のリクエストも対象としています)。
また、「一般的な」エラーページは、egまたは<error-code>
の500
代わりににマップする必要があることに注意してください。そうしないと、などでラップされたすべての例外が一般的なエラーページに表示されます。web.xmlのjava.lang.Throwableエラーページに示されているViewExpiredExceptionも参照してください。<exception-type>
java.lang.Exception
java.lang.Throwable
ServletException
ViewExpiredException
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/errorpages/general.xhtml</location>
</error-page>
ステートレスビュー
まったく異なる代替手段は、JSFビューをステートレスモードで実行することです。このように、JSF状態は何も保存されず、ビューが期限切れになることはありませんが、要求ごとに最初から再構築されるだけです。transient
の属性を<f:view>
次のように設定すると、ステートレスビューをオンにできますtrue
。
<f:view transient="true">
</f:view>
このようにして、javax.faces.ViewState
隠しフィールドはMojarraの固定値を取得し"stateless"
ます(この時点ではMyFacesをチェックしていません)。この機能はMojarra2.1.19および2.2.0で導入されたものであり、古いバージョンでは使用できないことに注意してください。
その結果、ビュースコープのBeanを使用できなくなります。これで、リクエストスコープのBeanのように動作します。欠点の1つは、非表示の入力や緩い要求パラメーターをいじって、自分で状態を追跡する必要があることです。rendered
主に、、readonly
またはdisabled
ajaxイベントによって制御される属性を持つ入力フィールドを持つフォームが影響を受けます。
<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" />
も参照してください