Jsf javax.faces.application.ViewExpiredException:无法还原视图

Jsf javax.faces.application.ViewExpiredException:无法还原视图,jsf,jsf-2,logout,viewexpiredexception,Jsf,Jsf 2,Logout,Viewexpiredexception,我编写了具有容器管理安全性的简单应用程序。问题是当我登录并打开另一个我注销的页面,然后我返回到第一个页面,我点击任何链接等或刷新页面,我得到这个例外。我想这是正常的(或者可能不是:),因为我注销了,会话被破坏了。我应该怎么做才能将用户重定向到index.xhtml或login.xhtml,并避免他看到错误页面/消息 换句话说,我如何在注销后自动将其他页面重定向到索引/登录页面 这是: javax.faces.application.ViewExpiredException: viewId:/in

我编写了具有容器管理安全性的简单应用程序。问题是当我登录并打开另一个我注销的页面,然后我返回到第一个页面,我点击任何链接等或刷新页面,我得到这个例外。我想这是正常的(或者可能不是:),因为我注销了,会话被破坏了。我应该怎么做才能将用户重定向到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)
介绍 每当
javax.faces.STATE\u SAVING\u METHOD
设置为
server
(默认)时,将抛出
ViewExpiredException
,最终用户通过
在视图上发送HTTP POST请求,而关联的视图状态在会话中不再可用

视图状态被标识为
的隐藏输入字段
javax.faces.ViewState
的值。将状态保存方法设置为
server
,则只包含引用会话中序列化视图状态的视图状态ID。因此,当会话因某种原因而过期时(服务器端或客户端超时,或由于某种原因在浏览器中不再维护会话cookie,或在服务器中调用
HttpSession#invalidate()
,或由于中已知的会话cookie的服务器特定错误),然后序列化视图状态在会话中不再可用,最终用户将获得此异常。要了解会话的工作原理,请参见

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

当state saving方法设置为
client
时,
javax.faces.ViewState
隐藏输入字段包含整个序列化视图状态,因此当会话过期时,最终用户不会得到
viewexpiredeexception
。但是,这种情况仍然可能发生在群集环境中(“错误:MAC未验证”是症状)和/或当配置的客户端状态存在特定于实现的超时时和/或当服务器在重启期间重新生成AES密钥时,请参阅如何解决此问题

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

至于为什么JSF需要保存视图状态,请转到以下答案:

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

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

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


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

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

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

<!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>
无状态视图 另一个完全不同的选择是在无状态模式下运行JSF视图。这样,JSF状态的任何内容都不会被保存,视图也永远不会过期,而只是在每个请求上从头开始重新构建。通过将
transient
属性设置为
true
,可以打开无状态视图:


这样,
javax.faces.ViewState
隐藏字段将在Mojarra中获得一个固定值
“无状态”
(此时未检查MyFaces)。请注意,此功能在Mojarra 2.1.19和2.2.0中,在旧版本中不可用

结果是您不能再使用视图范围的bean了。它们现在的行为类似于请求范围的bean。缺点之一是,您必须通过修改隐藏的输入和/或松散的请求参数来自己跟踪状态。主要是那些带有
呈现
只读
禁用
属性且由ajax事件控制的输入字段的表单将受到影响

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


在模板客户端中:


<!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>
<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" />
<context-param>
   <param-name>com.sun.faces.enableRestoreView11Compatibility</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>
@ManagedBean
@ViewScoped
public class Login implements Serializable {
}
...
<factory>
  <exception-handler-factory>org.primefaces.extensions.component.ajaxerrorhandler.AjaxExceptionHandlerFactory</exception-handler-factory>
</factory>
...
...
<pe:ajaxErrorHandler />
...
<context-param>
        <param-name>org.ajax4jsf.handleViewExpiredOnClient</param-name> 
        <param-value>true</param-value>     
    </context-param>
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;
    }

}   
<application>
    <view-handler>com.demo.CustomViewHandler</view-handler>
</application>
<h:form enctype="multipart/form-data">
    <a4j:poll id="poll" interval="10000"/>
</h:form>