1. ホーム
  2. jsf

[解決済み] javax.faces.application.ViewExpiredException: ビューを復元できませんでした

2022-02-09 11:06:57

質問

私は、コンテナ管理されたセキュリティで簡単なアプリケーションを書きました。問題は、私がログインし、ログアウトした別のページを開いたときに、最初のページに戻り、任意のリンクなどをクリックするか、ページを更新すると、この例外が発生します。私はログアウトし、セッションが破壊されているので、それは正常だと思います(または多分そうではありません:)。ユーザーを例えばindex.xhtmlやlogin.xhtmlにリダイレクトして、そのエラーページやメッセージを見ずに済むようにするにはどうしたらよいでしょうか?

言い換えれば、ログアウト後に他のページを自動的にindex/loginページにリダイレクトするにはどうすればよいでしょうか?

ここにあります。

javax.faces.application.ViewExpiredException: viewId:/index.xhtml - View /index.xhtml could not be restored.
    at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:212)
    at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
    at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:110)
    at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:312)
    at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1523)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:343)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:215)
    at filter.HttpHttpsFilter.doFilter(HttpHttpsFilter.java:66)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:256)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:215)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:277)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:188)
    at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:641)
    at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:97)
    at com.sun.enterprise.web.PESessionLockingStandardPipeline.invoke(PESessionLockingStandardPipeline.java:85)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:185)
    at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:325)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:226)
    at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:165)
    at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:791)
    at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:693)
    at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:954)
    at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:170)
    at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:135)
    at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:102)
    at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:88)
    at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:76)
    at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:53)
    at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:57)
    at com.sun.grizzly.ContextTask.run(ContextTask.java:69)
    at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:330)
    at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:309)
    at java.lang.Thread.run(Thread.java:619)

解決方法は?

はじめに

その ViewExpiredException が投げられるたびに javax.faces.STATE_SAVING_METHOD に設定されます。 server (デフォルト) で、エンドユーザーがビューに HTTP POST リクエストを送信する際に <h:form><h:commandLink> , <h:commandButton> または <f:ajax> のように、関連するビューの状態がセッションで利用できなくなったときに、そのビューの状態を変更することができます。

ビューの状態は、隠された入力フィールドの値として識別されます。 javax.faces.ViewState<h:form> . 状態の保存方法を server これは、セッション内のシリアライズされたビューステートを参照するビューステートIDのみを含んでいます。そのため、セッションが期限切れになったり、以下のような理由で不在になった場合、 ...

  • セッションオブジェクトがサーバーでタイムアウトした
  • クライアントでセッション Cookie がタイムアウトした
  • クライアントでセッションクッキーが削除された
  • HttpSession#invalidate() がサーバーで呼び出される
  • SameSite=None がセッション・クッキーで欠落している(したがって、例えば クローム は、サードパーティサイト(例:支払い)がコールバックURL経由であなたのサイトに戻るとき、それらを一緒に送信しません)

...その場合、シリアライズされたビューの状態はセッションで利用できなくなり、エンドユーザーはこの例外を受け取ります。セッションの動作を理解するために、次の項目も参照してください。 サーブレットはどのように動作するのですか?インスタンス化、セッション、共有変数、マルチスレッド .

また、JSFがセッションに保存するビューの量には制限があります。制限に達すると、最も最近に使用されたビューが期限切れになります。以下も参照してください。 com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews .

状態保存方法を client を指定すると、その javax.faces.ViewState 非表示の入力フィールドには、代わりにシリアライズされたビューの状態全体が含まれるため、エンドユーザーは ViewExpiredException を使用すると、セッションが終了したときに しかし、クラスタ環境ではまだ起こる可能性があり ("ERROR: MAC did not verify" がその症状)、クライアント側の状態に実装特有のタイムアウトがある場合、サーバが再起動時にAESキーを再 生成する場合、などがあります。 クラスタ環境で状態保存方式がクライアントに設定され、ユーザーセッションが有効なときにViewExpiredExceptionを取得する。 解決方法

解決方法に関わらず、以下のことを確認してください。 ない 使用 enableRestoreView11Compatibility .これは、元のビューの状態を復元することは一切ありません。これは基本的に、ビューと関連するすべてのビュースコープされたビーンをゼロから再作成するため、元のデータ (状態) をすべて失うことになります。アプリケーションは混乱した方法で動作するため ("Hey, where are my input values...?") 、これはユーザーエクスペリエンスにとって非常に悪いことです。ステートレス・ビューを使うか <o:enableRestorableView> そうすれば、すべてのビューで管理する代わりに、特定のビューだけで管理することができます。

については なぜ JSFはビューの状態を保存する必要があるため、この回答へどうぞ。 なぜJSFは、UIコンポーネントの状態をサーバーに保存するのですか?

ページ移動時のViewExpiredExceptionの回避

を回避するために ViewExpiredException に設定されている場合に、ログアウト後にナビゲートして戻る場合など。 server ログアウト後のPOSTリクエストのリダイレクトだけでは不十分です。また、ブラウザに次のように指示する必要があります。 ではない そうでなければ、ブラウザは、あなたがそれに対してGETリクエストを送るとき、サーバから新しいものを要求する代わりに、キャッシュからそれらを表示するかもしれません(例えば、バックボタンによって)。

javax.faces.ViewState キャッシュされたページの hidden フィールドには、現在のセッションではもう有効でないビュー状態 ID 値が含まれている可能性があります。もし、ページ間のナビゲーションに GET (通常のリンク/ボタン) ではなく POST (コマンドリンク/ボタン) を (ab) 使用していて、キャッシュされたページでそのようなコマンドリンク/ボタンをクリックすると、今度は ViewExpiredException .

JSF 2.0でログアウト後にリダイレクトを発生させるには、以下のどちらかを追加します。 <redirect /><navigation-case> を追加するか、あるいは ?faces-redirect=trueoutcome の値を指定します。

<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 pollや、同じページで壊れた画像に対して適切に実装された404エラーページ)にのみ発生することになります。以下も参照してください。 com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews を参照してください。もう一つの原因は、ランタイムクラスパスに重複した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>

(その 0content はリダイレクトまでの秒数を表します。 0 ということは、「すぐにリダイレクトする」という意味です。 3 を指定すると、ブラウザはリダイレクトを行うまで3秒間待機します)

ajaxリクエスト時の例外処理には、特別な ExceptionHandler . 参照 JSF/PrimeFacesのajaxリクエストにおけるセッションタイムアウトとViewExpiredExceptionの処理について . 次のサイトでライブの例を見ることができます。 オムニフェース FullAjaxExceptionHandler ショーケースページ (これは、非ajaxリクエストもカバーします)。

また、一般的なエラーページのマッピングは <error-code>500 の代わりに <exception-type> の、例えば java.lang.Exception または java.lang.Throwable でラップされたすべての例外は ServletException のように ViewExpiredException を実行しても、一般的なエラーページで終わってしまいます。以下も参照してください。 web.xml の java.lang.Throwable エラーページで表示される 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 という固定値を取得します。 "stateless" をMojarraに追加しました(現時点ではMyFacesは未確認)。なお、この機能は 導入 は、Mojarra 2.1.19 および 2.2.0 に収録されており、それ以前のバージョンでは使用できません。

その結果、ビューをスコープとしたビーンは使用できなくなりました。リクエストスコープドビーンズと同じように動作するようになります。欠点は、隠し入力や緩いリクエストパラメータを操作することで、 状態を自分で追跡しなければならないことです。主に、入力フィールドを持つフォームで 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" />

こちらもご覧ください