javax.faces.application.ViewExpiredException:ビューを復元できませんでした


175

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

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

ここにあります:

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)

回答:


353

前書き

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=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ポーリングまたは不適切に実装された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>

0in contentはリダイレクトまでの秒数を表し0ます。つまり、「すぐにリダイレクトする」という意味です。たとえば、リダイレクトを使用3してブラウザーに3秒待機させることができます)

ajaxリクエスト中の例外の処理には特別なが必要であることに注意してくださいExceptionHandlerJSF / PrimeFaces ajaxリクエストでのセッションタイムアウトとViewExpiredException処理も参照してください。OmniFaces FullAjaxExceptionHandlerショーケースページでライブサンプルを見つけることができます(これは非ajaxリクエストもカバーしています)。

また、あなたの「一般的な」エラーページが上にマップされなければならないことに注意<error-code>する500のではなく、<exception-type>例えばのjava.lang.Exceptionjava.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イベントによって制御されるrenderedreadonlyまたは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" />

こちらもご覧ください


Java ee 6の暗黙のナビゲーションでそれを行うにはどうすればよいですか?faces-configは使用しません。
l245c4l 2010

1
おお、JSF 2.0を使用していますか?あなたはあなたの質問でそれを述べるべきだったのです!に追加?faces-redirect=trueしますoutcome。それに応じて答えを更新しました。
BalusC 2010

はい、私はjava ee :)で始めたばかりで、すべてのナビゲーションでfaces-redirect = trueを使用しています。私はh:commandLinkを、それに関連付けられたアクションがある場合にのみ使用します。たとえば、ログアウトリンク...セッションを無効にしてログインにリダイレクトするアクションString logout()がありますが、ログインしていてログアウトしているページでは機能せず、例外がスローされます:(
l245c4l

1
再度ありがとうございます。申し訳ありません。:)でも、少なくともすぐに専門的な答えがすぐに得られました:p
l245c4l

1
@LS:ただし、POSTの有効期限が切れた後に[戻る]ボタンを押して別のPOSTリクエストを呼び出そうとする場合は常に、フィルターは必須です。そうでなければ、これは直感的にこの例外を引き起こします。
BalusC、

56

以下に行を追加してみましたweb.xmlか?

<context-param>
   <param-name>com.sun.faces.enableRestoreView11Compatibility</param-name>
   <param-value>true</param-value>
</context-param>

この問題が発生したとき、これは非常に効果的であることがわかりました。


1
それも私のために働いた。答えてくれてありがとう。これの目的は何ですか?
MartK

2
正確には思い出せませんが、ICEFacesのWebサイトでこの解決策を見つけました。
Mike GH

パーティーには少し遅れるかもしれませんが、これもうまくいきました。ありがとう!
stellarossa 2013

4
これはJSF 1.2またはJSF 2に対してのみ定義されていますか?
SRy

17
これにより、ビューが期限切れになると例外がスローされなくなり、リクエストが続行されますが、JSFはビューステートを復元したり、関連するビュースコープのBeanを見つけたりできません。このトランザクションはステートレスJSFのように動作し、「wtf?」を回避するためにPOSTリクエストパラメータに基づいてビューステートを自分で復元する必要があります。フォーム送信の処理時のエンドユーザーの経験が予期せず応答しました。これを特定のJSFページのみに適用する場合<o:enableRestorableView>は、アプリケーション全体のコンテキストパラメータの代わりにOmniFaces を使用してください。
BalusC

5

まずweb.xmlを変更する前に、ManagedBeanを確認する必要がありますimplements Serializable

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

特にMyFacesを使用する場合


3

Richfacesでマルチパートフォームを回避する:

<h:form enctype="multipart/form-data">
    <a4j:poll id="poll" interval="10000"/>
</h:form>

Richfacesを使用している場合、マルチパートフォーム内のajaxリクエストがリクエストごとに新しいビューIDを返すことがわかりました。

デバッグする方法:

各ajaxリクエストで、ビューIDが返されます。これは、ビューIDが常に同じである限り問題ありません。リクエストごとに新しいビューIDを取得する場合、問題があり、修正する必要があります。


世論調査で遊んでたときに、それが期限切れにユーザーセッションを防ぐことができ、注意してください...
ジョナサンSimas

0

独自のカスタムAjaxExceptionHandlerまたはprimefaces-extensionsを使用できます

faces-config.xmlを更新する

...
<factory>
  <exception-handler-factory>org.primefaces.extensions.component.ajaxerrorhandler.AjaxExceptionHandlerFactory</exception-handler-factory>
</factory>
...

あなたのjsfページに次のコードを追加してください

...
<pe:ajaxErrorHandler />
...

0

このエラーが発生しました:javax.faces.application.ViewExpiredException。異なるリクエストを使用すると、サーバーを再起動した後でも、同じJsessionIdを持つリクエストが見つかりました。これはブラウザのキャッシュが原因です。ブラウザを閉じて試してみるだけで動作します。


0

ページがx時間アイドル状態になると、ビューが期限切れになり、javax.faces.application.ViewExpiredExceptionがスローされてこれが発生しないようにします。1つの解決策は、ViewHandlerを拡張するCustomViewHandlerを作成し、restoreViewメソッドをオーバーライドして、他のすべてのメソッドを親

import java.io.IOException;
import javax.faces.FacesException;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;

public class CustomViewHandler extends ViewHandler {
    private ViewHandler parent;

    public CustomViewHandler(ViewHandler parent) {
        //System.out.println("CustomViewHandler.CustomViewHandler():Parent View Handler:"+parent.getClass());
        this.parent = parent;
    }

    @Override 
    public UIViewRoot restoreView(FacesContext facesContext, String viewId) {
    /**
     * {@link javax.faces.application.ViewExpiredException}. This happens only  when we try to logout from timed out pages.
     */
        UIViewRoot root = null;
        root = parent.restoreView(facesContext, viewId);
        if(root == null) {
            root = createView(facesContext, viewId);
        }
        return root;
    }

    @Override
    public Locale calculateLocale(FacesContext facesContext) {
        return parent.calculateLocale(facesContext);
    }

    @Override
    public String calculateRenderKitId(FacesContext facesContext) {
        String renderKitId = parent.calculateRenderKitId(facesContext);
        //System.out.println("CustomViewHandler.calculateRenderKitId():RenderKitId: "+renderKitId);
        return renderKitId;
    }

    @Override
    public UIViewRoot createView(FacesContext facesContext, String viewId) {
        return parent.createView(facesContext, viewId);
    }

    @Override
    public String getActionURL(FacesContext facesContext, String actionId) {
        return parent.getActionURL(facesContext, actionId);
    }

    @Override
    public String getResourceURL(FacesContext facesContext, String resId) {
        return parent.getResourceURL(facesContext, resId);
    }

    @Override
    public void renderView(FacesContext facesContext, UIViewRoot viewId) throws IOException, FacesException {
        parent.renderView(facesContext, viewId);
    }

    @Override
    public void writeState(FacesContext facesContext) throws IOException {
        parent.writeState(facesContext);
    }

    public ViewHandler getParent() {
        return parent;
    }

}   

次に、それをfaces-config.xmlに追加する必要があります

<application>
    <view-handler>com.demo.CustomViewHandler</view-handler>
</application>

以下のリンクの元の回答をありがとう:http : //www.gregbugaj.com/?p=164


このアプローチでは、ビュースコープのBeanは復元されません。
BalusC

-2

この行をweb.xmlに追加してください

<context-param>
        <param-name>org.ajax4jsf.handleViewExpiredOnClient</param-name> 
        <param-value>true</param-value>     
    </context-param>

4
コードが何をしているかについての説明とコンテキストを含めると、答えがより役に立ちます。
Palpatim 14年

1
これが正しい答えであっても、StackOverflowは説明なしでこのような答えを推奨しません。なぜそれが機能するのかについての情報を追加することは、コミュニティにとって役立つでしょう。
RacerNerd 14年

-2

私は自分でこの問題に遭遇しました。これは、アプリケーションのすべてのリクエストをフィルタリングする、作成したFilterの副作用によるものであることに気付きました。特定のリクエストのみを選択するようにフィルターを変更した直後、この問題は発生しませんでした。アプリケーションでそのようなフィルタをチェックし、それらがどのように動作するかを確認することは良いことかもしれません。


-3

次の設定をweb.xmlに追加すると解決しました。

<context-param>
    <param-name>com.sun.faces.numberOfViewsInSession</param-name>
    <param-value>500</param-value>
</context-param>
<context-param>
    <param-name>com.sun.faces.numberOfLogicalViews</param-name>
    <param-value>500</param-value>
</context-param>

6
ひどいアドバイス。十分なメモリがあり、エンドユーザーが常に最大500のブラウザータブを開いており、ブラウザーの戻るボタンを以前の同期ポストバックまで最大500回押している場合にのみ、これを行ってください。
BalusC 2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.