小编典典

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

all

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

阅读 62

收藏
2022-08-05

共1个答案

小编典典

介绍

每当设置为(默认)并且最终用户通过with或对视图发送 HTTP POST
请求时,ViewExpiredException将抛出,而会话中不再提供相关的视图状态。javax.faces.STATE_SAVING_METHOD``server``<h:form>``<h:commandLink>``<h:commandButton>``<f:ajax>

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

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

…然后序列化视图状态在会话中不再可用,最终用户将收到此异常。

JSF
将在会话中存储的视图数量也有限制。当达到限制时,最近最少使用的视图将过期。

将状态保存方法设置为client时,javax.faces.ViewState隐藏的输入字段包含整个序列化视图状态,因此最终用户不会ViewExpiredException在会话到期时得到
a。但是,它仍然可能发生在集群环境中(“错误:MAC 未验证”是有症状的)和/或在配置的客户端状态存在特定于实现的超时和/或服务器在重启期间重新生成 AES
密钥时

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

在页面导航中避免 ViewExpiredException

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

缓存页面的javax.faces.ViewState隐藏字段可能包含在当前会话中不再有效的视图状态 ID 值。如果您(ab)使用
POST(命令链接/按钮)而不是
GET(常规链接/按钮)进行页面到页面导航,并在缓存页面上单击这样的命令链接/按钮,那么这将依次失败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映射到 servlet
名称的它,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同一页面的损坏图像上的错误页面)。。

处理 ViewExpiredException

当您在另一个选项卡/窗口中注销时,在已经在某个浏览器选项卡/窗口中打开的任意页面上执行 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>

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

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

0incontent表示重定向前的秒数,0因此表示“立即重定向”,您可以使用 eg3让浏览器在重定向时等待 3 秒)

请注意,在 ajax 请求期间处理异常需要特殊的ExceptionHandler.

另请注意,您的“一般”错误页面应映射到<error-code>of500而不是<exception-type>eg
java.lang.Exceptionor
,否则包含在中java.lang.Throwable的所有异常都将最终出现在一般错误页面中。

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

无状态视图

一个完全不同的替代方法是以无状态模式运行 JSF 视图。这种方式不会保存任何 JSF
状态,并且视图永远不会过期,而只是在每次请求时从头开始重建。transient您可以通过将属性设置为<f:view>来打开无状态视图true

<f:view transient="true">

</f:view>

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

结果是您不能再使用视图范围的 bean。它们现在的行为类似于请求范围的
bean。缺点之一是您必须通过摆弄隐藏的输入和/或松散的请求参数来自己跟踪状态。主要是那些输入字段带有rendered,readonlydisabled
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>在 a<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" />
2022-08-05