异常: 无法还原视图

我编写了具有容器管理安全性的简单应用程序。问题是,当我登录并打开另一个页面,我注销,然后我回到第一页,我点击任何链接等或刷新页面,我得到这个异常。我想这是正常的(或者可能不是:) ,因为我退出了,会话被销毁了。我应该怎么做才能将用户重定向到 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)
286887 次浏览

简介

javax.faces.STATE_SAVING_METHOD被设置为 server(默认)并且终端用户通过 <h:commandLink><h:commandButton><f:ajax>通过 <h:form>向视图发送 HTTP POST 请求时,ViewExpiredException将被抛出,而相关的视图状态在会话中不再可用。

视图状态被标识为 <h:form>的隐藏输入字段 javax.faces.ViewState的值。如果状态保存方法设置为 server,那么它只包含引用会话中序列化视图状态的视图状态 ID。因此,当会话因下列原因之一而过期或缺席时..。

  • 会话对象在服务器中超时
  • 会话 cookie 在客户端超时
  • 会话 cookie 在客户端中被删除
  • 在服务器中调用 HttpSession#invalidate()
  • 会话 cookie 中缺少 SameSite=None(因此,当第三方站点(例如付款)通过回调 URL 导航回到您的站点时,铬合金不会发送它们)

... 那么序列化视图状态在会话中不再可用,终端用户将得到这个异常。要了解会话的工作原理,请参阅 Servlet 是如何工作的? 实例化、会话、共享变量和多线程

JSF 在会话中存储的视图数量也有限制。当达到限制时,最近使用最少的视图将过期。参见 NumberOfViewsInSession vs com.sun.faces.numberOfLogicalView

如果状态保存方法设置为 client,那么 javax.faces.ViewState隐藏输入字段将包含整个序列化视图状态,因此当会话到期时,终端用户不会得到 ViewExpiredException。然而,它仍然可以发生在集群环境(“ ERROR: MAC 没有验证”是症状)和/或当客户端状态配置和/或当服务器在重启期间重新生成 AES 密钥时,请参阅 在集群环境中获取 ViewExpiredException,同时将状态保存方法设置为客户端,并且用户会话有效如何解决它。

不管解决方案是什么,请确保使用 enableRestoreView11Compatibility执行 没有。它根本不还原原始视图状态。它基本上是从头开始重新创建视图和所有关联的视图作用域 bean,因此丢失了所有原始数据(状态)。因为应用程序会以一种令人困惑的方式运行(“嘿,我的输入值在哪里。.),这是非常糟糕的用户体验。最好使用无状态视图或 <o:enableRestorableView>,这样就可以只在特定视图上管理它,而不是在所有视图上。

至于 为什么 JSF 需要保存视图状态,请看这个答案: 为什么 JSF 在服务器上保存 UI 组件的状态?

避免页面导航中的 ViewExpiredException 异常

为了避免 ViewExpiredException,例如当状态保存设置为 server时在注销后导航回来,仅在注销后重定向 POST 请求是不够的。您还需要指示浏览器 没有缓存动态 JSF 页面,否则当您发送 GET 请求时,浏览器可能会从缓存中显示这些页面,而不是从服务器请求一个新的页面(例如通过后退按钮)。

缓存页的 javax.faces.ViewState隐藏字段可能包含一个在当前会话中无效的视图状态 ID 值。如果您(ab)使用 POST (命令链接/按钮)而不是 GET (常规链接/按钮)进行页面到页面的导航,并在缓存页面上单击这样的命令链接/按钮,那么这将因为 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 页面,请创建一个映射到 FacesServlet的 servlet 名称上的 Filter,并添加所需的响应头以禁用浏览器缓存。例如。

@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时,为了避免刷新当前页面时出现 ViewExpiredException,你不仅需要确保只通过 GET (常规链接/按钮)执行页面到页面的导航,还需要确保只使用 ajax 提交表单。如果您以同步方式(非 ajax)提交表单,那么您最好使视图无状态(请参阅后面的部分) ,或者在 POST 之后发送重定向(请参阅前面的部分)。

在页面刷新时使用 ViewExpiredException是非常罕见的默认配置。只有当会话中存储的 JSF 视图数量达到限制时,才会发生这种情况。所以,它只会发生在你手动设置的限制太低,或者你不断创建新的视图在“背景”(例如由一个糟糕的实现 Ajax 轮询在同一个页面,或由一个糟糕的实现404错误页面在同一个页面的破碎图像)。有关该限制的详细信息,请参阅 NumberOfViewsInSession vs com.sun.faces.numberOfLogicalView。另一个原因是运行时类路径中的重复 JSF 库相互冲突。我们的 JSF 维基页面中概述了安装 JSF 的正确过程。

处理 ViewExpiredException

当你想要处理一个不可避免的 ViewExpiredException在一个任意页面上的一个 POST 操作之后,这个操作已经在某个浏览器标签/窗口中打开,而你在另一个标签/窗口中登出,那么你想要在 web.xml中为这个操作指定一个 error-page,它会进入一个“你的会话超时”页面。例如。

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

(content中的 0表示重定向前的秒数,因此 0表示“立即重定向”,你可以使用例如 3让浏览器等待3秒)

注意,在 ajax 请求期间处理异常需要一个特殊的 ExceptionHandler。参见 JSF/PrimeFaces ajax 请求的会话超时和 ViewExpiredException 处理。您可以在 OmniFaces FullAjaxExceptionHandler展示页面中找到一个实时示例(这也包括非 ajax 请求)。

还要注意,你的“一般”错误页面应该映射在 500<error-code>上,而不是 <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 状态,视图也永远不会过期,只是在每次请求时从头开始重新构建。您可以通过将 <f:view>transient属性设置为 true来打开无状态视图:

<f:view transient="true">


</f:view>

这样,在 Mojarra,abc0隐藏字段将得到一个固定的 "stateless"值(此时还没有选中 myFaces)。请注意,这个特性是 Mojarra 2.1.19和2.2.0版本中的 介绍,在旧版本中不可用。

其结果是您不能再使用视图范围内的 bean。它们现在的行为将类似于请求范围内的 bean。缺点之一是您必须自己通过处理隐藏的输入和/或松散的请求参数来跟踪状态。主要是那些带有 renderedreadonlydisabled属性的输入字段的表单会受到 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" />

参见

你有没有试过在你的 web.xml下面加几行?

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

当我遇到这个问题时,我发现这是非常有效的。

我将下面的配置添加到 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>

首先,在更改 Xml之前,您必须确保您的 ManagedBean implements Serializable:

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

特别是如果你使用 MyFaces

您可以使用自己的定制 AjaxExceptionHandler 或 primefaces 扩展

更新您的 faces-config. xml

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

在 jsf 页面中添加以下代码

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

请在 web.xml 中添加这一行 对我有用

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

我自己也遇到了这个问题,我意识到这是因为我创建的 Filter 的副作用,它过滤了应用程序上的所有请求。只要我修改过滤器,只挑选某些请求,就不会出现这个问题。最好在应用程序中检查这种过滤器,看看它们是如何工作的。

我得到了这个错误: javax.faces.application。ViewExpiredException 异常。当我使用不同的请求时,我发现那些请求具有相同的 JsessionId,即使在重新启动服务器之后也是如此。 所以这是由于浏览器缓存。只要关闭浏览器并尝试,它将工作。

当页面空闲 X 段时间后,视图将过期并抛出 javax.faces.application。ViewExpiredException 来防止这种情况发生 一种解决方案是创建扩展 ViewHandler 的 CustomViewHandler 并重写 restoreView 方法,所有其他方法都将委托给 Parent

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

在 Richfaces 避免多部分形式:

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

如果您正在使用 Richfaces,我发现在 multipart 表单中的 ajax 请求会在每个请求上返回一个新的 View ID。

如何调试:

对于每个 ajax 请求,都会返回一个 View ID,只要 View ID 始终相同就可以了。如果您在每个请求上获得一个新的 ViewID,那么就会出现问题,并且必须修复。

我在 JAVA EE 8中使用 Primefaces 的 AjaxExceptionHandler标签解决了这个问题,这可以从 Primefaces 7或更高版本获得(我正在使用和测试11个版本)。非常简单,您可以像 BalusC 建议的那样将它与定制的 ExceptionHandlerWrapper 组合在一起。如果需要页面重新加载是自动的,可以像这样使用 onShow 事件。

<p:ajaxExceptionHandler type="javax.faces.application.ViewExpiredException"
update="viewExpiredDialog"
onexception="PF('viewExpiredDialog').show();" />


<p:dialog id="viewExpiredDialog" header="Session expired"
widgetVar="viewExpiredDialog" height="250px"
onShow="document.location.href = document.location.href;">
<h3>Reloading page</h3>
<p>Message...</p>
<!--Here, you decide that you need-->
<h:commandButton value="Reload" action="index?faces-redirect=true" />
<a href="#{request.contextPath}/login.xhtml">Reload</a>.
</p:dialog>

将此配置添加到 Faces-config. xml文件。请参阅 例外处理程序错误处理

<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.3" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd">
<application>
<el-resolver>
org.primefaces.application.exceptionhandler.PrimeExceptionHandlerELResolver
</el-resolver>
</application>
<factory>
<exception-handler-factory>
org.primefaces.application.exceptionhandler.PrimeExceptionHandlerFactory
</exception-handler-factory>
</factory>
</faces-config>

瞧,这就像上了发条一样,祝你好运。