web-dev-qa-db-ja.com

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

私はコンテナー管理セキュリティーを備えた簡単なアプリケーションを書きました。問題は、ログインしてログアウトした別のページを開いたときに、最初のページに戻ってリンクなどをクリックしたり、ページを更新したりすると、この例外が発生することです。ログアウトしてセッションが破壊されたので、それは普通のことだと思います(またはそうではないかもしれません)。ユーザーを例えば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)
163
l245c4l

前書き

ViewExpiredExceptionは、javax.faces.STATE_SAVING_METHODserver(デフォルト)に設定され、エンドユーザーが<h:form>を介してビューにHTTP POSTリクエストを送信するたびにスローされます<h:commandLink><h:commandButton>、または<f:ajax>を使用して、関連付けられたビューステートはセッションで使用できなくなります。

ビューステートは、javax.faces.ViewStateの隠し入力フィールド<h:form>の値として識別されます。状態保存メソッドをserverに設定すると、これにはセッション内のシリアル化されたビューステートを参照するビューステートIDのみが含まれます。そのため、何らかの理由でセッションが期限切れになった場合(サーバー側またはクライアント側でタイムアウトしたか、ブラウザで何らかの理由でセッションCookieが維持されなくなった場合、サーバーでHttpSession#invalidate()を呼び出した場合、またはサーバー固有 WildFly )で知られているセッションCookieのバグ。この場合、シリアル化されたビューステートはセッションで使用できなくなり、エンドユーザーはこの例外を取得します。セッションの動作を理解するには、 サーブレットの動作方法?インスタンス化、セッション、共有変数、マルチスレッド も参照してください。

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

状態保存メソッドをclientに設定すると、javax.faces.ViewState隠し入力フィールドにシリアル化されたビューステート全体が含まれるため、セッションの有効期限が切れたときにエンドユーザーはViewExpiredExceptionを取得しません。ただし、クラスター環境(「エラー:MACが確認できませんでした」は症状)および/または構成されたクライアント側の状態に実装固有のタイムアウトがある場合、および/またはサーバーが再起動中にAESキーを再生成する場合に発生する可能性があります、 状態保存メソッドがクライアントに設定され、ユーザーセッションが有効なときにクラスター環境でViewExpiredExceptionを取得する 解決方法も参照してください。

解決策に関係なく、必ずnot use enableRestoreView11Compatibilityを実行してください。元のビューステートはまったく復元されません。基本的にビューと関連するすべてのビュースコープBeanをゼロから再作成し、これにより元のデータ(状態)をすべて失います。アプリケーションは混乱した方法で動作するので(「おい、私の入力値はどこですか?」)、これはユーザーエクスペリエンスにとって非常に悪いです。代わりに、ステートレスビューまたは<o:enableRestorableView>を使用して、すべてのビューではなく特定のビューでのみ管理できるようにします。

whyJSFがビューステートを保存する必要があるので、この答えに進んでください: なぜJSFはUIコンポーネントの状態をサーバーに保存するのですか?

ページナビゲーションでのViewExpiredExceptionの回避

ViewExpiredExceptionを避けるために状態の保存がserverに設定されているときにログアウト後にナビゲートし、ログアウト後にPOST要求のみをリダイレクトするだけでは不十分です。また、ブラウザに動的JSFページをnotキャッシュしないように指示する必要があります。そうしないと、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 vs com.Sun.faces.numberOfLogicalViews も参照してください。もう1つの原因は、ランタイムクラスパス内で重複するJSFライブラリが競合していることです。 JSFをインストールするための正しい手順は、 JSF wikiページ で概説されています。

ViewExpiredExceptionの処理

別のタブ/ウィンドウでログアウトしているときにブラウザのタブ/ウィンドウで既に開かれている任意のページでPOSTアクションの後に避けられないViewExpiredExceptionを処理する場合、 「あなたのセッションがタイムアウトしました」ページに移動するerror-pageweb.xmlを指定したいです。例えば。

<error-page>
    <exception-type>javax.faces.application.ViewExpiredException</exception-type>
    <location>/WEB-INF/errorpages/expired.xhtml</location>
</error-page>

ホームページまたはログインページに実際にredirectさらにリダイレクトする場合は、必要に応じてエラーページでメタリフレッシュヘッダーを使用します。

<!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>

content0はリダイレクト前の秒数を表します。したがって、0は「すぐにリダイレクト」を意味します。たとえば、3を使用してブラウザにリダイレクトで3秒待機させます)

Ajaxリクエスト中に例外を処理するには、特別なExceptionHandlerが必要です。 JSF/PrimeFaces ajaxリクエストでのセッションタイムアウトおよびViewExpiredException処理 も参照してください。ライブ例は OmniFaces FullAjaxExceptionHandler showcase page で見つけることができます(これは、ajax以外のリクエストもカバーします)。

また、「一般的な」エラーページは、<error-code>ではなく500 of <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の状態は保存されず、ビューの有効期限は切れず、リクエストごとにゼロから再構築されます。 <f:view>transient属性をtrueに設定すると、ステートレスビューをオンにできます。

<f:view transient="true">

</f:view>

これにより、javax.faces.ViewState隠しフィールドはMojarraで"stateless"の固定値を取得します(この時点では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>などのネストされたコンテンツだけでなく、entireビューにも適用されることに注意してください。

こちらもご覧ください


無関係具体的な問題に対して、純粋なページ間ナビゲーションに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" />

こちらもご覧ください

335
BalusC

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

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

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

55
Mike GH

web.xmlを変更する前に、まずあなたがしなければならないことはあなたのManagedBeanのimplements Serializableを確かめることです:

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

特にあなたがMyFacesを使うなら

5
ZuzEL

Richfacesのマルチパートフォームは避けてください

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

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

デバッグ方法:

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

3
tak3shi

独自のカスタムAjaxExceptionHandlerまたはprimefaces-extensionsを使用している

faces-config.xmlを更新してください

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

jsfページに以下のコードを追加してください

...
<pe:ajaxErrorHandler />
...
0
myset

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

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

0
user4924157

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

0
rahul