私はコンテナー管理セキュリティーを備えた簡単なアプリケーションを書きました。問題は、ログインしてログアウトした別のページを開いたときに、最初のページに戻ってリンクなどをクリックしたり、ページを更新したりすると、この例外が発生することです。ログアウトしてセッションが破壊されたので、それは普通のことだと思います(またはそうではないかもしれません)。ユーザーを例えば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)
ViewExpiredException
は、javax.faces.STATE_SAVING_METHOD
がserver
(デフォルト)に設定され、エンドユーザーが<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
を避けるために状態の保存がserver
に設定されているときにログアウト後にナビゲートし、ログアウト後にPOST要求のみをリダイレクトするだけでは不十分です。また、ブラウザに動的JSFページをnotキャッシュしないように指示する必要があります。そうしないと、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
に設定されている場合、現在のページを更新するときにserver
を回避するには、GETのみでページ間ナビゲーションを実行する必要があります(通常のリンク/ボタン)、ただし、フォームの送信にajaxのみを使用していることを確認する必要もあります。とにかくフォームを同期(非ajax)で送信する場合は、ビューをステートレスにする(後のセクションを参照)か、POST(前のセクションを参照)の後にリダイレクトを送信するのが最善です。
ページの更新時にViewExpiredException
を使用することは、デフォルト設定では非常にまれなケースです。 JSFがセッションに保存するビューの量の制限に達した場合にのみ発生します。そのため、手動で制限方法を低く設定した場合、または「バックグラウンド」で新しいビューを継続的に作成している場合にのみ発生します(たとえば、同じページに実装されたajaxポーリングまたは404同じページの壊れた画像のエラーページ)。その制限の詳細については、 com.Sun.faces.numberOfViewsInSession vs com.Sun.faces.numberOfLogicalViews も参照してください。もう1つの原因は、ランタイムクラスパス内で重複するJSFライブラリが競合していることです。 JSFをインストールするための正しい手順は、 JSF wikiページ で概説されています。
別のタブ/ウィンドウでログアウトしているときにブラウザのタブ/ウィンドウで既に開かれている任意のページでPOSTアクションの後に避けられないViewExpiredException
を処理する場合、 「あなたのセッションがタイムアウトしました」ページに移動するerror-page
のweb.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>
(content
の0
はリダイレクト前の秒数を表します。したがって、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イベントによって制御される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>
などのネストされたコンテンツだけでなく、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" />
あなたのweb.xml
に以下の行を追加してみましたか?
<context-param>
<param-name>com.Sun.faces.enableRestoreView11Compatibility</param-name>
<param-value>true</param-value>
</context-param>
この問題に遭遇したとき、私はこれが非常に効果的であることがわかりました。
web.xmlを変更する前に、まずあなたがしなければならないことはあなたのManagedBeanのimplements Serializable
を確かめることです:
@ManagedBean
@ViewScoped
public class Login implements Serializable {
}
特にあなたがMyFacesを使うなら
Richfacesのマルチパートフォームは避けてください
<h:form enctype="multipart/form-data">
<a4j:poll id="poll" interval="10000"/>
</h:form>
Richfacesを使用している場合、マルチパートフォーム内のajaxリクエストがリクエストごとに新しいView IDを返すことがわかりました。
デバッグ方法:
AjaxリクエストごとにビューIDが返されます。ビューIDが常に同じであれば問題ありません。リクエストごとに新しいビューIDを取得した場合は、問題があるため修正する必要があります。
独自のカスタムAjaxExceptionHandlerまたはprimefaces-extensionsを使用している
faces-config.xmlを更新してください
...
<factory>
<exception-handler-factory>org.primefaces.extensions.component.ajaxerrorhandler.AjaxExceptionHandlerFactory</exception-handler-factory>
</factory>
...
jsfページに以下のコードを追加してください
...
<pe:ajaxErrorHandler />
...
これが起こらないようにするために、ページが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
Javax.faces.application.ViewExpiredExceptionが発生していました。異なるリクエストを使用すると、サーバーを再起動しても同じJsessionIdを持つリクエストが見つかりました。つまり、これはブラウザのキャッシュによるものです。ブラウザを閉じて試してみてください、それは動作します。