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

问题描述 投票:163回答:10

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

介绍

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

视图状态被标识为javax.faces.ViewState的隐藏输入字段<h:form>的值。将状态保存方法设置为server时,它仅包含引用会话中序列化视图状态的视图状态ID。因此,当会话由于某种原因(在服务器或客户端超时,或者在浏览器中由于某种原因不再维护会话cookie,或者在服务器中调用HttpSession#invalidate(),或者由于服务器特定的会话cookie错误)如WildFly中所知,那么序列化视图状态在会话中不再可用,并且最终用户将获得此异常。要了解会话的工作情况,请参阅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

例如,为了避免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页面,请创建Filter,该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); } // ... } 的servlet名称上,并添加所需的响应头以禁用浏览器缓存。例如。

ViewExpiredException

在页面刷新时避免ViewExpiredException

为了在状态保存设置为server时刷新当前页面时避免使用ViewExpiredException,您不仅需要确保通过GET(常规链接/按钮)执行页面到页面导航,而且还需要确保您专门使用ajax提交表单。如果您同时提交表单(非ajax),那么您最好使视图无状态(请参阅后面的部分),或者在POST后发送重定向(请参阅上一节)。

在页面刷新上使用com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews是默认配置,这是一种非常罕见的情况。只有当JSF将在会话中存储的视图数量限制被命中时,才会发生这种情况。因此,只有当您手动将限制方式设置得太低,或者您在“后台”中不断创建新视图时才会发生这种情况(例如,在同一页面中执行错误的ajax轮询或执行错误的404同一页面的损坏图像上的错误页面)。有关该限制的详细信息,另请参阅our JSF wiki page。另一个原因是在运行时类路径中有重复的JSF库相互冲突。 ViewExpiredException概述了安装JSF的正确过程。

处理ViewExpiredException

如果你想在一个任意页面上的POST操作后处理一个不可避免的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>

如果您打算实际重定向到主页或登录页面,请在错误页面中使用元刷新标头。

0

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

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

另请注意,您的“常规”错误页面应映射到500<exception-type>而不是java.lang.Exception,例如java.lang.ThrowableServletExceptionViewExpiredException,否则包含在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> ,仍将在一般错误页面中结束。另见transient

<f:view>

无国籍观点

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

"stateless"

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

结果是你不能再使用视图范围的bean了。它们现在的行为类似于请求范围的bean。缺点之一是您必须通过摆弄隐藏的输入和/或松散的请求参数来自己跟踪状态。主要是那些带有由ajax事件控制的disabled<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>包裹在ViewExpiredException shown in java.lang.Throwable error-page in web.xml中以使其成为有条件的。请注意,它将应用于整个视图,而不仅仅应用于嵌套内容,例如上例中的Check if session exists JSF

也可以看看


与具体问题无关,使用HTTP POST进行纯页面到页面导航不是非常用户/ SEO友好。在JSF 2.0中,您应该更喜欢<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" /> ,用于普通的页面到页面导航。

所以不是例如

When should I use h:outputLink instead of h:commandLink?

更好

Difference between h:button and h:commandButton

See also


-3
投票

我将以下配置添加到web.xml并得到解决。

qazxswpoi

55
投票

您是否尝试在implements Serializable下面添加以下行?

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

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


5
投票

首先,在更改web.xml之前,您需要做的是确保您的ManagedBean <h:form enctype="multipart/form-data"> <a4j:poll id="poll" interval="10000"/> </h:form>

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

特别是如果你使用MyFaces


3
投票

在Richfaces中避免使用多部分表单:

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

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

如何调试:

在每个ajax请求上返回一个View ID,只要View ID始终相同,就可以了。如果您在每个请求上获得新的视图ID,则存在问题,必须修复。


0
投票

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

更新faces-config.xml

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

}   

在jsf页面中添加以下代码

<application>
    <view-handler>com.demo.CustomViewHandler</view-handler>
</application>

0
投票

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


0
投票

当我们的页面闲置x个时间时,视图将过期并抛出javax.faces.application.ViewExpiredException以防止这种情况发生一个解决方案是创建扩展ViewHandler并覆盖restoreView方法的CustomViewHandler,所有其他方法都被委托给亲

http://www.gregbugaj.com/?p=164

然后你需要将它添加到faces-config.xml

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

感谢以下链接的原始答案:<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>


-2
投票

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

qazxswpoi

-2
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.