javax.faces.application.ViewExpiredException:无法恢复视图 [英] javax.faces.application.ViewExpiredException: View could not be restored

查看:35
本文介绍了javax.faces.application.ViewExpiredException:无法恢复视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我编写了具有容器管理安全性的简单应用程序.问题是当我登录并打开另一个我退出的页面时,然后我回到第一页并单击任何链接等或刷新页面时,我收到此异常.我想这是正常的(或者可能不是:))因为我注销了会话被破坏了.我应该怎么做才能将用户重定向到例如 index.xhtml 或 login.xhtml 并避免他看到该错误页面/消息?

I have written simple application with container-managed security. The problem is when I log in and open another page on which I logout, then I come back to first page and I click on any link etc or refresh page I get this exception. I guess it's normal (or maybe not:)) because I logged out and session is destroyed. What should I do to redirect user to for example index.xhtml or login.xhtml and save him from seeing that error page/message?

换句话说,我如何在注销后自动将其他页面重定向到索引/登录页面?

In other words how can I automatically redirect other pages to index/login page after I log out?

这是:

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(默认)并且最终用户发送 HTTP POST 时抛出通过 请求视图,而关联的视图状态在会话中不再可用.

Introduction

The ViewExpiredException will be thrown whenever the javax.faces.STATE_SAVING_METHOD is set to server (default) and the enduser sends a HTTP POST request on a view via <h:form> with <h:commandLink>, <h:commandButton> or <f:ajax>, while the associated view state isn't available in the session anymore.

视图状态被标识为 的隐藏输入字段 javax.faces.ViewState 的值.将状态保存方法设置为 server,这仅包含引用会话中序列化视图状态的视图状态 ID.因此,当会话因以下原因之一过期或缺席时......

The view state is identified as value of a hidden input field javax.faces.ViewState of the <h:form>. With the state saving method set to server, this contains only the view state ID which references a serialized view state in the session. So, when the session is expired or absent for one of the following reasons ...

  • 会话对象在服务器中超时
  • 会话 cookie 在客户端超时
  • 会话 cookie 在客户端被删除
  • HttpSession#invalidate() 在服务器中被调用
  • SameSite=None 在会话 cookie 中丢失(因此例如 Chrome 不会在第 3 方网站(例如付款)通过回调 URL 导航回您的网站时发送它们)
  • session object is timed out in server
  • session cookie is timed out in client
  • session cookie is deleted in client
  • HttpSession#invalidate() is called in server
  • SameSite=None is missing on session cookie (and thus e.g. Chrome won't send them along when a 3rd party site (e.g. payment) navigates back to your site via a callback URL)

... 然后序列化视图状态在会话中不再可用,最终用户将收到此异常.要了解会话的工作,另请参阅 servlet 如何工作?实例化、会话、共享变量和多线程.

... then the serialized view state is not available anymore in the session and the enduser will get this exception. To understand the working of the session, see also How do servlets work? Instantiation, sessions, shared variables and multithreading.

JSF 将在会话中存储的视图数量也有限制.当达到限制时,最近最少使用的视图将过期.另请参阅 com.sun.faces.numberOfViewsInSession 与 com.sun.faces.numberOfLogicalViews.

There is also a limit on the amount of views JSF will store in the session. When the limit is hit, then the least recently used view will be expired. See also com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews.

状态保存方法设置为 client 时,javax.faces.ViewState 隐藏输入字段包含整个序列化视图状态,因此最终用户不会得到会话过期时的 ViewExpiredException.然而,它仍然可能发生在集群环境中(错误:MAC 没有验证"是有症状的)和/或当配置的客户端状态存在特定于实现的超时和/或当服务器在此期间重新生成 AES 密钥时重启,另见 Getting ViewExpiredException in clustered environment while state save method is设置为客户端和用户会话有效如何解决.

With the state saving method set to client, the javax.faces.ViewState hidden input field contains instead the whole serialized view state, so the enduser won't get a ViewExpiredException when the session expires. It can however still happen on a cluster environment ("ERROR: MAC did not verify" is symptomatic) and/or when there's a implementation-specific timeout on the client side state configured and/or when server re-generates the AES key during restart, see also Getting ViewExpiredException in clustered environment while state saving method is set to client and user session is valid how to solve it.

无论解决方案如何,请确保您不要使用enableRestoreView11Compatibility.它根本不会恢复原始视图状态.它基本上从头开始重新创建视图和所有关联的视图范围 bean,从而丢失所有原始数据(状态).由于应用程序将以令人困惑的方式运行(嘿,我的输入值在哪里??"),这对用户体验来说非常糟糕.最好使用无状态视图或 <o:enableRestorableView> 代替,以便您可以仅在特定视图上而不是在所有视图上管理它.

Regardless of the solution, make sure you do not use enableRestoreView11Compatibility. it does not at all restore the original view state. It basically recreates the view and all associated view scoped beans from scratch and hereby thus losing all of original data (state). As the application will behave in a confusing way ("Hey, where are my input values..??"), this is very bad for user experience. Better use stateless views or <o:enableRestorableView> instead so you can manage it on a specific view only instead of on all views.

至于 为什么 JSF 需要保存视图状态,请前往以下答案:为什么 JSF 将 UI 组件的状态保存在服务器上?

As to the why JSF needs to save view state, head to this answer: Why JSF saves the state of UI components on server?

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

In order to avoid ViewExpiredException when e.g. navigating back after logout when the state saving is set to server, only redirecting the POST request after logout is not sufficient. You also need to instruct the browser to not cache the dynamic JSF pages, otherwise the browser may show them from the cache instead of requesting a fresh one from the server when you send a GET request on it (e.g. by back button).

缓存页面的 javax.faces.ViewState 隐藏字段可能包含在当前会话中不再有效的视图状态 ID 值.如果您(ab)使用 POST(命令链接/按钮)而不是 GET(常规链接/按钮)进行页面到页面导航,并在缓存页面上单击这样的命令链接/按钮,那么这将反过来失败并返回 ViewExpiredException.

The javax.faces.ViewState hidden field of the cached page may contain a view state ID value which is not valid anymore in the current session. If you're (ab)using POST (command links/buttons) instead of GET (regular links/buttons) for page-to-page navigation, and click such a command link/button on the cached page, then this will in turn fail with a ViewExpiredException.

要在 JSF 2.0 中注销后触发重定向,请将 添加到有问题的 (如果有),或将 ?faces-redirect=true 添加到 outcome 值.

To fire a redirect after logout in JSF 2.0, either add <redirect /> to the <navigation-case> in question (if any), or add ?faces-redirect=true to the outcome value.

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

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

要指示浏览器不缓存动态 JSF 页面,请创建一个 Filter,它映射到 FacesServlet 的 servlet 名称并添加所需的响应头以禁用浏览器缓存.例如

To instruct the browser to not cache the dynamic JSF pages, create a Filter which is mapped on the servlet name of the FacesServlet and adds the needed response headers to disable the browser cache. E.g.

@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,你不仅需要确保你正在执行page-to-页面导航仅由 GET(常规链接/按钮)进行,但您还需要确保仅使用 ajax 提交表单.如果您无论如何都要同步提交表单(非 ajax),那么您最好要么使视图无状态(参见后面的部分),要么在 POST 后发送重定向(参见前面的部分).

Avoiding ViewExpiredException on page refresh

In order to avoid ViewExpiredException when refreshing the current page when the state saving is set to server, you not only need to make sure you are performing page-to-page navigation exclusively by GET (regular links/buttons), but you also need to make sure that you are exclusively using ajax to submit the forms. If you're submitting the form synchronously (non-ajax) anyway, then you'd best either make the view stateless (see later section), or to send a redirect after POST (see previous section).

在页面刷新时出现 ViewExpiredException 在默认配置中是一种非常罕见的情况.只有在达到 JSF 将在会话中存储的视图数量限制时才会发生这种情况.因此,只有当您手动将该限制设置得太低,或者您在背景"中不断创建新视图时才会发生这种情况.(例如,通过在同一页面中错误实施的 ajax 轮询或通过在同一页面的损坏图像上错误地实施 404 错误页面).另请参阅 com.sun.faces.numberOfViewsInSession 与 com.sun.faces.numberOfLogicalViews 有关该限制的详细信息.另一个原因是运行时类路径中有重复的 JSF 库相互冲突.我们的 JSF wiki 页面中概述了安装 JSF 的正确过程.

Having a ViewExpiredException on page refresh is in default configuration a very rare case. It can only happen when the limit on the amount of views JSF will store in the session is hit. So, it will only happen when you've manually set that limit way too low, or that you're continuously creating new views in the "background" (e.g. by a badly implemented ajax poll in the same page or by a badly implemented 404 error page on broken images of the same page). See also com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews for detail on that limit. Another cause is having duplicate JSF libraries in runtime classpath conflicting each other. The correct procedure to install JSF is outlined in our JSF wiki page.

当您在某个浏览器选项卡/窗口中已打开的任意页面上执行 POST 操作后想要处理不可避免的 ViewExpiredException,而您在另一个选项卡/窗口中注销时,那么您'想在 web.xml 中指定一个 error-page 去你的会话超时";页.例如

When you want to handle an unavoidable ViewExpiredException after a POST action on an arbitrary page which was already opened in some browser tab/window while you're logged out in another tab/window, then you'd like to specify an error-page for that in web.xml which goes to a "Your session is timed out" page. E.g.

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

如有必要,请在错误页面中使用元刷新标题,以防您实际上重定向到主页或登录页面.

Use if necessary a meta refresh header in the error page in case you intend to actually redirect further to home or login 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 秒)

(the 0 in content represents the amount of seconds before redirect, 0 thus means "redirect immediately", you can use e.g. 3 to let the browser wait 3 seconds with the redirect)

注意在ajax请求期间处理异常需要一个特殊的ExceptionHandler.另请参阅会话超时和对 JSF/PrimeFaces ajax 请求的 ViewExpiredException 处理.您可以在 OmniFaces FullAjaxExceptionHandler 展示页面 中找到现场示例(这也是涵盖非 ajax 请求).

Note that handling exceptions during ajax requests requires a special ExceptionHandler. See also Session timeout and ViewExpiredException handling on JSF/PrimeFaces ajax request. You can find a live example at OmniFaces FullAjaxExceptionHandler showcase page (this also covers non-ajax requests).

另请注意,您的将军"错误页面应该映射到 500 而不是 例如java.lang.Exceptionjava.lang.Throwable,否则包含在 ServletException 中的所有异常,例如 ViewExpiredException仍然最终出现在一般错误页面中.另请参阅 java.lang.Throwable 中显示的 ViewExpiredExceptionweb.xml 中的错误页面.

Also note that your "general" error page should be mapped on <error-code> of 500 instead of an <exception-type> of e.g. java.lang.Exception or java.lang.Throwable, otherwise all exceptions wrapped in ServletException such as ViewExpiredException would still end up in the general error page. See also ViewExpiredException shown in java.lang.Throwable error-page in web.xml.

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

无状态视图

另一种完全不同的选择是以无状态模式运行 JSF 视图.这样,JSF 的任何状态都不会被保存并且视图永远不会过期,而只是在每次请求时从头开始重建.您可以通过将 transient 属性设置为 true 来打开无状态视图:

Stateless views

A completely different alternative is to run JSF views in stateless mode. This way nothing of JSF state will be saved and the views will never expire, but just be rebuilt from scratch on every request. You can turn on stateless views by setting the transient attribute of <f:view> to true:

<f:view transient="true">

</f:view>

这样 javax.faces.ViewState 隐藏字段将在 Mojarra 中获得 stateless" 的固定值(此时尚未检查 MyFaces).请注意,此功能是在 Mojarra 2.1.19 和 2.2.0 中引入并且在旧版本中不可用.

This way the javax.faces.ViewState hidden field will get a fixed value of "stateless" in Mojarra (have not checked MyFaces at this point). Note that this feature was introduced in Mojarra 2.1.19 and 2.2.0 and is not available in older versions.

结果是您不能再使用视图范围的 bean.它们现在的行为类似于请求范围的 bean.缺点之一是您必须通过摆弄隐藏的输入和/或松散的请求参数来自己跟踪状态.主要是那些带有 renderedreadonlydisabled 属性的输入字段的表单会受到影响.

The consequence is that you cannot use view scoped beans anymore. They will now behave like request scoped beans. One of the disadvantages is that you have to track the state yourself by fiddling with hidden inputs and/or loose request parameters. Mainly those forms with input fields with rendered, readonly or disabled attributes which are controlled by ajax events will be affected.

请注意, 不一定需要在整个视图中都是唯一的和/或仅驻留在主模板中.重新声明并将其嵌套在模板客户端中也是完全合法的.它基本上是扩展"的.然后是父 .例如.在主模板中:

Note that the <f:view> does not necessarily need to be unique throughout the view and/or reside in the master template only. It's also completely legit to redeclare and nest it in a template client. It basically "extends" the parent <f:view> then. E.g. in master template:

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

您甚至可以将 包装在 中以使其成为条件.请注意,它适用于整个视图,而不仅适用于嵌套内容,例如上面示例中的 .

You can even wrap the <f:view> in a <c:if> to make it conditional. Note that it would apply on the entire view, not only on the nested contents, such as the <h:form> in above example.

与具体问题无关,使用 HTTP POST 进行纯页面到页面导航对用户/SEO 不太友好.在 JSF 2.0 中,你应该更喜欢 而不是 用于普通的页面到页面导航.

Unrelated to the concrete problem, using HTTP POST for pure page-to-page navigation isn't very user/SEO friendly. In JSF 2.0 you should really prefer <h:link> or <h:button> over the <h:commandXxx> ones for plain vanilla page-to-page navigation.

所以而不是例如

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

另见

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆