ChatGPT解决这个技术问题 Extra ChatGPT

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

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

B
BalusC

介绍

每当 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()

SameSite=None 在会话 cookie 中丢失(因此,当第 3 方站点(例如付款)通过回调 URL 导航回您的站点时,Chrome 不会发送它们)

...然后序列化视图状态在会话中不再可用,最终用户将收到此异常。要了解会话的工作原理,另请参阅 How do servlets work? Instantiation, sessions, shared variables and multithreading

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

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

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

至于 为什么 JSF 需要保存视图状态,请参阅以下答案:Why JSF saves the state of UI components on server?

在页面导航中避免 ViewExpiredException

为了避免在状态保存设置为server 时注销后导航时出现ViewExpiredException,仅在注销后重定向 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同一页面的损坏图像上的错误页面)。有关该限制的详细信息,另请参阅 com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews。另一个原因是运行时类路径中有重复的 JSF 库相互冲突。 our JSF wiki page 中概述了安装 JSF 的正确过程。

处理 ViewExpiredException

如果您想在对已在某个浏览器选项卡/窗口中打开的任意页面执行 POST 操作后处理不可避免的 ViewExpiredException,而您已在另一个选项卡/窗口中注销,那么您想指定一个 {2 } 对于 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>

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

请注意,在 ajax 请求期间处理异常需要特殊的 ExceptionHandler。另见Session timeout and ViewExpiredException handling on JSF/PrimeFaces ajax request。您可以在 OmniFaces FullAjaxExceptionHandler showcase page 找到一个实时示例(这也包括非 ajax 请求)。

另请注意,您的“一般”错误页面应映射到 500<error-code>,而不是 <exception-type>,例如 java.lang.Exceptionjava.lang.Throwable,否则包含在 ServletException 中的所有异常(例如 ViewExpiredException)都会仍然出现在一般错误页面中。另见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 状态,并且视图永远不会过期,而只是在每次请求时从头开始重建。您可以通过将 <f:view>transient 属性设置为 true 来打开无状态视图:

<f:view transient="true">

</f:view>

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

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

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

也可以看看

Web.xml 中的 java.lang.Throwable 错误页面中显示的 ViewExpiredException

检查会话是否存在 JSF

JSF/PrimeFaces ajax 请求上的会话超时和 ViewExpiredException 处理

与具体问题无关,使用 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" />

也可以看看

什么时候应该使用 h:outputLink 而不是 h:commandLink?

h:button 和 h:commandButton 之间的区别

如何在 JSF 中导航?如何使 URL 反映当前页面(而不是上一个页面)


如何在 java ee 6 中使用隐式导航来做到这一点?我不使用faces-config。
哦,您使用的是 JSF 2.0?你应该在你的问题中提到这一点!将 ?faces-redirect=true 添加到 outcome。我已经相应地更新了答案。
是的,我刚开始使用 java ee:),我在所有导航中都使用 faces-redirect=true。只有当我有与之相关的操作时,我才使用 h:commandLink。例如注销链接...我有操作字符串 logout(),我在其中使会话无效并重定向到登录,但它在我登录的页面上不起作用并且目前正在注销并抛出该异常:(
再次感谢并为此感到抱歉:)但至少我很快就得到了快速而专业的答案:p
@LS:但是,只要在过期的 POST 后按下后退按钮并尝试对其调用另一个 POST 请求,过滤器仍然是强制性的。否则,这将不直观地导致此异常。
A
Apurv

您是否尝试在 web.xml 中添加以下行?

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

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


它也对我有用。感谢你的回答。这样做的目的是什么?
不记得了,但我在 ICEFaces 网站上找到了这个解决方案。
我参加聚会可能有点晚了,但这也对我有用。谢谢!
这是否也仅针对 JSF 1.2 或 JSF 2 定义?
这将在视图过期时停止抛出异常并继续请求,但 JSF 仍然无法恢复视图状态,也无法找到任何关联的视图范围 bean。此事务的行为类似于无状态 JSF,您需要根据 POST 请求参数自行恢复视图状态以避免“wtf?”最终用户在处理表单提交时的体验意外响应。如果您只想在特定的 JSF 页面上应用它,那么请使用 OmniFaces <o:enableRestorableView> 而不是应用程序范围的上下文参数。
Z
ZuzEL

在更改 web.xml 之前,您首先要做的是确保您的 ManagedBean implements Serializable

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

特别是如果您使用 MyFaces


t
tak3shi

避免 Richfaces 中的多部分表单:

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

如果您使用的是 Richfaces,我发现多部分表单中的 ajax 请求会在每个请求中返回一个新的视图 ID。

如何调试:

在每个 ajax 请求上都会返回一个 View ID,只要 View ID 始终相同就可以了。如果您在每个请求上都获得了一个新的视图 ID,那么就存在问题并且必须解决。


玩民意调查时要小心,它可以防止您的用户会话过期..
D
Dagon

我在 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 文件中。请参阅 ExceptionHandlerError Handling

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

瞧,这就像发条一样工作。问候。


C
Cristian Florescu

您可以使用自己的自定义 AjaxExceptionHandler 或 primefaces-extensions

更新你的 faces-config.xml

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

在您的 jsf 页面中添加以下代码

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

r
rahul

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


R
ROMANIA_engineer

当我们的页面空闲 x 时间时,视图将过期并抛出 javax.faces.application.ViewExpiredException 以防止这种情况发生,一种解决方案是创建扩展 ViewHandler 的 CustomViewHandler 并覆盖 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


这种方法不会恢复视图范围的 bean。
h
harshal

请将此行添加到您的 web.xml 它对我有用

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

如果您包含有关代码正在做什么的解释和上下文,您的答案将更加有用。
即使这是正确的答案,StackOverflow 也不鼓励在没有解释的情况下做出这样的答案。添加更多有关其工作原理的信息将对社区有所帮助。
j
javshak

我自己遇到了这个问题,并意识到这是因为我创建的过滤器的副作用是过滤应用程序上的所有请求。一旦我修改过滤器以仅选择某些请求,就没有发生此问题。在您的应用程序中检查此类过滤器并查看它们的行为可能会很好。


M
Mohamed Ali

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

可怕的建议。仅当您有足够的内存并且您的最终用户确实始终打开多达 500 个浏览器选项卡并按浏览器的后退按钮多达 500 次之前的同步回发时才这样做。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅