javax.faces.application.ViewExpiredException: View could not be restored

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 using <h:commandLink> or <h:commandButton> on a view, while the associated view state isn't available in the session anymore. The view state is identified by 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 for some reason (either timed out in server or client side, or the session cookie is not maintained anymore for some reason in browser, or by calling HttpSession#invalidate() in server), then the serialized view state is not available anymore in the session and the enduser will get this exception.

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.

In order to prevent ViewExpiredException 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). If you're using POST for page-to-page navigation, this will in turn fail.

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:commandButtonvalue="Logout"action="logout?faces-redirect=true"/>

or

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

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.

 

// Must match <servlet-name> of your FacesServlet.
@WebFilter(servletNames={"Faces Servlet"})
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);
    }

    // ...
}

When you want to handle the ViewExpiredException 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 index/login page or maybe a "Your session is timed out" page. E.g.

 

<error-page>
    <exception-type>javax.faces.application.ViewExpiredException</exception-type>
    <location>/login.xhtml</location>
</error-page>

 

Note that handling exceptions during ajax requests requires a special ExceptionHandler. See also the last "See also" link below for a detailed answer on that.

 

See also:

 

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.

See also

你可能感兴趣的:(JSF)