javax.faces.application.ViewExpiredException:无法恢复视图

我用容器pipe理的安全性编写了简单的应用程序。 问题是当我login并打开另一个页面,我退出,然后回到第一页,我点击任何链接等或刷新页面我得到这个exception。 我想这是正常的(或者不是:)),因为我注销和会话被销毁。 我应该怎么做redirect用户为例如index.xhtml或login.xhtml并保存他看到错误页面/消息?

换句话说,我注销后如何自动将其他页面redirect到索引/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) 

介绍

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

视图状态被标识为<h:form>的隐藏input字段javax.faces.ViewState的值。 将状态保存方法设置为server ,仅包含引用会话中序列化视图状态的视图状态ID。 所以,当会话由于某种原因(在服务器端或客户端超时,或者由于某种原因在浏览器中不再维护会话cookie,或者通过在服务器中调用HttpSession#invalidate()或由于服务器特定如在WildFly中已知会话cookie的错误),那么序列化的视图状态在会话中不再可用,并且最终用户将得到这个exception。 要了解会话的工作,请参阅servlet如何工作? 实例化,会话,共享variables和multithreading 。

JSF将在会话中存储的视图数量也有限制。 当限制被击中,那么最近最less使用的视图将会过期。 另请参阅com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews 。

通过将状态保存方法设置为clientjavax.faces.ViewState隐藏的input字段将包含整个序列化的视图状态,因此,当会话过期时, ViewExpiredException不会得到ViewExpiredException 。 但是,在集群环境(“错误:MAC无法validation”是有症状的)和/或在客户端configuration的状态发生特定于实现的超时和/或服务器在重新启动期间重新生成AES密钥时,它仍可能发生,另请参阅在集群环境中获取ViewExpiredException,同时将状态保存方法设置为客户端,并且用户会话有效如何解决该问题。

不pipe解决scheme如何,请确保不要使用enableRestoreView11Compatibility 。 它根本不能恢复原来的视图状态。 它基本上从头开始重新创build视图和所有关联的视图范围的bean,从而丢失了所有的原始数据(状态)。 由于应用程序的行为混乱(“嘿,我的input值在哪里?”),这对用户体验来说是非常糟糕的。 更好地使用无状态视图或者<o:enableRestorableView>来代替所有视图,只能在特定的视图上进行pipe理。

至于为什么 JSF需要保存视图状态,可以这样回答: 为什么JSF将UI组件的状态保存在服务器上?

在页面导航上避免ViewExpiredException

为了避免ViewExpiredException ,例如,当状态保存设置为server ,在注销后返回导航,只有在注销后redirectPOST请求是不够的。 您还需要指示浏览器cachingdynamicJSF页面,否则浏览器可能会从caching中显示它们,而不是在发送GET请求时(例如,通过后退button)从服务器请求新的一个。

caching页面的javax.faces.ViewState隐藏字段可能包含当前会话中无效的视图状态ID值。 如果您(AB)使用POST(命令链接/button)而不是GET(常规链接/button)进行页面到页面导航,然后单击caching页面上的这样的命令链接/button,则这将反过来以ViewExpiredException失败。

要在JSF 2.0中注销后触发redirect,可以将<redirect />添加到有问题的<navigation-case> (如果有),或将?faces-redirect=trueoutcome值。

 <h:commandButton value="Logout" action="logout?faces-redirect=true" /> 

要么

 public String logout() { // ... return "index?faces-redirect=true"; } 

要指示浏览器不cachingdynamicJSF页面,请创build映射到FacesServlet的servlet名称上的Filter ,并添加所需的响应标头以禁用浏览器caching。 例如

 @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(常规链接/button)完成页面到页面导航,还需要确保你完全使用Ajax来提交表单。 如果您同时提交表单(非ajax),那么您最好使视图无状态(请参阅后面的部分),或者在POST之后发送redirect(请参阅上一节)。

在页面刷新时出现ViewExpiredException是一种非常罕见的情况。 它只能在JSF存储在会话中的视图数量限制被触发时发生。 所以,只有当你手动设置的限制太低,或者你不断地在后台创build新的视图时(例如,通过一个糟糕的轮询),才会发生。 另请参阅com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews 。 另一个原因是在运行时类path中存在重复的JSF库彼此冲突。 在我们的JSF wiki页面中概述了安装JSF的正确过程。

处理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到家庭或login页面,则在错误页面中使用必要的元刷新标题。

 <!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表示redirect之前的秒数, 0表示“立即redirect”,您可以使用例如3让浏览器等待3秒redirect)

请注意,在ajax请求期间处理exception需要一个特殊的ExceptionHandler 。 请参阅JSF / PrimeFaces ajax请求上的会话超时和ViewExpiredException处理 。 你可以在OmniFaces FullAjaxExceptionHandler展示页面find一个实例(这也包括非ajax请求)。

另外请注意,您的“常规”错误页面应该映射到500 <error-code>而不是<exception-type>例如java.lang.Exceptionjava.lang.Throwable ,否则所有包装在ServletExceptionexceptionViewExpiredException仍然会在一般错误页面中结束。 另请参见web.xml中的java.lang.Throwable错误页面中显示的ViewExpiredException 。

 <error-page> <error-code>500</error-code> <location>/WEB-INF/errorpages/general.xhtml</location> </error-page> 

无状态的意见

一个完全不同的select是在无状态模式下运行JSF视图。 这样,JSF状态的任何内容都不会被保存,视图永远不会过期,而是在每个请求上从头开始重build。 您可以通过将<f:view>transient属性设置为true来打开无状态视图:

 <f:view transient="true"> </f:view> 

这样, javax.faces.ViewState隐藏字段将在Mojarra中获得一个固定的"stateless"值(此时还没有检查过MyFaces)。 请注意,该function是在Mojarra 2.1.19和2.2.0中引入的 ,并且在旧版本中不可用。

结果是,你不能再使用视图范围的bean。 他们现在将像请求范围的bean一样工作。 其中一个缺点是你必须通过摆弄隐藏的input和/或松散的请求参数来跟踪状态。 主要是那些input字段具有由ajax事件控制的renderedreadonlydisabled属性的表单将受到影响。

请注意, <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> 

你甚至可以在<c:if>包装<f:view> <c:if>来使其成为有条件的。 请注意,它将应用于整个视图,不仅适用于嵌套的内容,如上例中的<h:form>

也可以看看

  • web.xml中的java.lang.Throwable错误页面中显示的ViewExpiredException
  • 检查会话是否存在JSF
  • JSF / PrimeFaces ajax请求上的会话超时和ViewExpiredException处理

具体问题无关 ,使用HTTP POST进行纯页面到页面导航并不是非常友好的用户/search引擎优化。 在JSF 2.0中,您应该更喜欢通过<h:commandXxx><h:link><h:button>进行普通的vanilla页面到页面导航。

所以,而不是例如

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

也可以看看

  • 什么时候应该使用h:outputLink而不是h:commandLink?
  • h:button和h:commandButton之间的区别
  • 如何在JSF中导航? 如何使URL反映当前页面(而不是以前的页面)

你有没有尝试添加下面的行到你的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。

如何debugging:

在每个ajax请求中都返回一个View ID,只要View 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 /> ... 

我得到这个错误:javax.faces.application.ViewExpiredException.When我使用不同的请求时,我发现那些具有相同的JsessionId,即使重新启动服务器。 所以这是由于浏览器caching。 只要closures浏览器,尝试,它会工作。

当我们的页面空闲了一段时间,视图将过期并抛出javax.faces.application.ViewExpiredException来防止这种情况发生。一种解决scheme是创buildCustomViewHandler,它扩展了ViewHandler并覆盖了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

我将以下configuration添加到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> 

请在你的web.xml中添加这行代码适用于我

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

我自己遇到了这个问题,并意识到这是因为我创build的filter的副作用,它过滤了应用程序的所有请求。 只要我修改filter只select某些请求,这个问题没有发生。 在你的应用程序中检查这样的filter可能是很好的,看看它们是如何工作的。