javax.faces.application.ViewExpiredException: la vista no se pudo restaurar

175

He escrito una aplicación simple con seguridad gestionada por contenedor. El problema es que cuando inicio sesión y abro otra página en la que cierro sesión, vuelvo a la primera página y hago clic en cualquier enlace, etc. o actualizo la página y obtengo esta excepción. Supongo que es normal (o tal vez no :)) porque me desconecté y la sesión se destruyó. ¿Qué debo hacer para redirigir al usuario a, por ejemplo, index.xhtml o login.xhtml y evitar que vea esa página / mensaje de error?

En otras palabras, ¿cómo puedo redirigir automáticamente otras páginas a la página de índice / inicio de sesión después de cerrar sesión?

Aquí está:

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)
l245c4l
fuente

Respuestas:

353

Introducción

Se ViewExpiredExceptionlanzará cada vez que javax.faces.STATE_SAVING_METHODse establezca en server(predeterminado) y el usuario final envíe una solicitud POST HTTP en una vista a través <h:form>de <h:commandLink>, <h:commandButton>o <f:ajax>, mientras el estado de vista asociado ya no esté disponible en la sesión.

El estado de la vista se identifica como el valor de un campo javax.faces.ViewStatede entrada oculto de <h:form>. Con el método de ahorro de estado establecido en server, este contiene solo el ID del estado de vista que hace referencia a un estado de vista serializada en la sesión. Entonces, cuando la sesión caduca por algún motivo (se agotó el tiempo de espera en el servidor o en el lado del cliente, o la cookie de sesión ya no se mantiene por algún motivo en el navegador, o al llamar HttpSession#invalidate()al servidor, o debido a un error específico del servidor con cookies de sesión como conocido en WildFly ), el estado de la vista serializada ya no está disponible en la sesión y el usuario final obtendrá esta excepción. Para comprender el funcionamiento de la sesión, consulte también ¿Cómo funcionan los servlets? Instanciación, sesiones, variables compartidas y multihilo .

También hay un límite en la cantidad de vistas que JSF almacenará en la sesión. Cuando se alcanza el límite, la vista utilizada menos recientemente expirará. Consulte también com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews .

Con el método de ahorro de estado establecido en client, el javax.faces.ViewStatecampo de entrada oculto contiene en su lugar todo el estado de la vista serializada, por lo que el usuario final no recibirá un mensaje ViewExpiredExceptioncuando expire la sesión. Sin embargo, aún puede ocurrir en un entorno de clúster ("ERROR: MAC no verificó" es sintomático) y / o cuando hay un tiempo de espera específico de implementación en el estado del lado del cliente configurado y / o cuando el servidor vuelve a generar la clave AES durante el reinicio , consulte también Obtención de ViewExpiredException en un entorno en clúster mientras el método de ahorro de estado se establece en el cliente y la sesión del usuario es válida para resolverlo.

Independientemente de la solución, asegúrese de no usar enableRestoreView11Compatibility. no restaura en absoluto el estado de vista original. Básicamente, recrea la vista y todos los beans de ámbito de vista asociados desde cero y, por lo tanto, pierde todos los datos originales (estado). Como la aplicación se comportará de una manera confusa ("Oye, ¿dónde están mis valores de entrada ...?"), Esto es muy malo para la experiencia del usuario. Mejor use vistas sin estado o en su <o:enableRestorableView>lugar para que pueda administrarlo en una vista específica solo en lugar de en todas las vistas.

En cuanto a por qué JSF necesita guardar el estado de vista, diríjase a esta respuesta: ¿Por qué JSF guarda el estado de los componentes de la interfaz de usuario en el servidor?

Evitar ViewExpiredException en la navegación de la página

Para evitar ViewExpiredExceptioncuando, por ejemplo, navega hacia atrás después de cerrar sesión cuando el estado está guardado en server, solo redirigir la solicitud POST después de cerrar sesión no es suficiente. También debe indicar al navegador que no almacene en caché las páginas JSF dinámicas, de lo contrario, el navegador puede mostrarlas desde la memoria caché en lugar de solicitar una nueva del servidor cuando envía una solicitud GET (por ejemplo, con el botón Atrás).

El javax.faces.ViewStatecampo oculto de la página en caché puede contener un valor de ID de estado de vista que ya no es válido en la sesión actual. Si (ab) usa POST (enlaces / botones de comando) en lugar de GET (enlaces / botones normales) para la navegación de página a página, y hace clic en dicho enlace / botón de comando en la página en caché, esto a su vez fallar con a ViewExpiredException.

Para activar una redirección después de cerrar sesión en JSF 2.0, agregue <redirect />a la <navigation-case>pregunta en cuestión (si corresponde) o agregue ?faces-redirect=trueal outcomevalor.

<h:commandButton value="Logout" action="logout?faces-redirect=true" />

o

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

Para indicar al navegador que no almacene en caché las páginas JSF dinámicas, cree una Filterque esté asignada en el nombre del servlet del FacesServlety agregue los encabezados de respuesta necesarios para deshabilitar la memoria caché del navegador. P.ej

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

    // ...
}

Evitar ViewExpiredException en la actualización de la página

Para evitar la ViewExpiredExceptionactualización de la página actual cuando el estado está guardado en server, no solo necesita asegurarse de que realiza la navegación de página a página exclusivamente por GET (enlaces / botones regulares), sino que también debe asegurarse que está utilizando exclusivamente ajax para enviar los formularios. Si de todos modos está enviando el formulario de forma síncrona (no ajax), lo mejor es que haga que la vista no tenga estado (consulte la sección posterior) o envíe una redirección después de POST (consulte la sección anterior).

Tener una ViewExpiredExceptionactualización en la página está en la configuración predeterminada, un caso muy raro. Solo puede suceder cuando se alcanza el límite de la cantidad de vistas que JSF almacenará en la sesión. Por lo tanto, solo sucederá cuando haya establecido manualmente ese límite demasiado bajo, o que esté creando continuamente nuevas vistas en el "fondo" (por ejemplo, mediante una encuesta ajax mal implementada en la misma página o por un 404 mal implementado página de error en imágenes rotas de la misma página). Consulte también com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews para obtener detalles sobre ese límite. Otra causa es tener bibliotecas JSF duplicadas en classpath en tiempo de ejecución en conflicto entre sí. El procedimiento correcto para instalar JSF se describe en nuestra página wiki de JSF .

Manejo de ViewExpiredException

Cuando se quiere manejar una inevitable ViewExpiredExceptiondespués de una acción de publicar en una página cualquiera que ya se abrió en alguna pestaña del navegador / ventana mientras está conectado a cabo en otra pestaña / ventana, a continuación, desea especificar una error-pagepara que, en web.xmllo que va a una página "Su sesión ha expirado". P.ej

<error-page>
    <exception-type>javax.faces.application.ViewExpiredException</exception-type>
    <location>/WEB-INF/errorpages/expired.xhtml</location>
</error-page>

Use, si es necesario, un encabezado de meta actualización en la página de error en caso de que tenga la intención de redirigir más a la página de inicio o inicio de sesión.

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

(la 0de contentrepresenta la cantidad de segundos antes de redirección, 0por lo tanto significa "redirigir inmediatamente", se puede utilizar por ejemplo 3para permitir que el navegador espera 3 segundos con la redirección)

Tenga en cuenta que el manejo de excepciones durante las solicitudes ajax requiere un especial ExceptionHandler. Consulte también Tiempo de espera de sesión y Manejo de la excepción ViewExpiredException en la solicitud ajax de JSF / PrimeFaces . Puede encontrar un ejemplo en vivo en la página de presentación de OmniFacesFullAjaxExceptionHandler (esto también cubre las solicitudes que no son de Ajax).

También tenga en cuenta que su página de error "general" debe asignarse en lugar <error-code>de en 500lugar de, <exception-type>por ejemplo , java.lang.Exceptiono java.lang.Throwable, de lo contrario, todas las excepciones envueltas en ServletExceptiontal como ViewExpiredExceptionterminarían en la página de error general. Consulte también ViewExpiredException que se muestra en java.lang.Throwable error-page en web.xml .

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

Vistas sin estado

Una alternativa completamente diferente es ejecutar vistas JSF en modo sin estado. De esta manera, nada del estado JSF se guardará y las vistas nunca caducarán, sino que se reconstruirán desde cero en cada solicitud. Puede activar las vistas sin estado configurando el transientatributo de <f:view>a true:

<f:view transient="true">

</f:view>

De esta forma, el javax.faces.ViewStatecampo oculto obtendrá un valor fijo de "stateless"en Mojarra (no he verificado MyFaces en este momento). Tenga en cuenta que esta característica se introdujo en Mojarra 2.1.19 y 2.2.0 y no está disponible en versiones anteriores.

La consecuencia es que ya no puede usar los beans de vista. Ahora se comportarán como frijoles con ámbito de solicitud. Una de las desventajas es que tiene que rastrear el estado usted mismo jugando con entradas ocultas y / o parámetros de solicitud sueltos. Principalmente aquellos formularios con campos de entrada con rendered, readonlyo disabledatributos que son controlados por eventos ajax serán afectados.

Tenga en cuenta que <f:view>no necesariamente tiene que ser único en toda la vista y / o residir solo en la plantilla maestra. También es completamente legítimo redeclararlo y anidarlo en un cliente de plantilla. Básicamente "extiende" al padre <f:view>entonces. Por ejemplo, en la plantilla maestra:

<f:view contentType="text/html">
    <ui:insert name="content" />
</f:view>

y en cliente de plantilla:

<ui:define name="content">
    <f:view transient="true">
        <h:form>...</h:form>
    </f:view>
</f:view>

Incluso puedes envolverlo <f:view>en a <c:if>para hacerlo condicional. Tenga en cuenta que se aplicaría en toda la vista, no solo en los contenidos anidados, como <h:form>en el ejemplo anterior.

Ver también


Sin relación con el problema concreto, el uso de HTTP POST para la navegación pura de página a página no es muy amigable para el usuario / SEO. En JSF 2.0 realmente debería preferir <h:link>o <h:button>sobre los que <h:commandXxx>están para la navegación simple de página a página.

Entonces, en lugar de, por ejemplo,

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

mejor hacerlo

<h:link value="Foo" outcome="foo" />
<h:link value="Bar" outcome="bar" />
<h:link value="Baz" outcome="baz" />

Ver también

BalusC
fuente
¿Cómo puedo hacerlo con navegación implícita en java ee 6? No uso faces-config.
l245c4l
1
¿Estás usando JSF 2.0? ¡Deberías haber mencionado eso en tu pregunta! Añadir ?faces-redirect=truea la outcome. He actualizado la respuesta en consecuencia.
BalusC
Sí, acabo de comenzar con java ee :) y estoy usando faces-redirect = true en todas mis navegaciones. Uso h: commandLink solo cuando tengo acciones asociadas con él. Por ejemplo Enlace de cierre de sesión ... Tengo una acción String logout () donde invalido la sesión y redirijo al inicio de sesión, pero no funciona en la página en la que estaba conectado y en este momento desconectado y arroja esa excepción :(
l245c4l
1
Gracias de nuevo y lo siento por eso :) pero al menos recibí una respuesta rápida y profesional en muy poco tiempo: p
l245c4l
1
@LS: Sin embargo, el filtro sigue siendo obligatorio para el caso cada vez que se presiona el botón Atrás después de una POST caducada e intenta invocar otra solicitud POST en él. De lo contrario, esto resultaría de manera no intuitiva en esta excepción.
BalusC
56

¿Has intentado agregar líneas a continuación a tu web.xml?

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

Encontré que esto era muy efectivo cuando encontré este problema.

Mike GH
fuente
1
A mí también me funcionó. Gracias por la respuesta. ¿Cuál es el propósito de esto?
MartK
2
No recuerdo exactamente, pero encontré esta solución en el sitio web de ICEFaces.
Mike GH
Puede que llegue un poco tarde a la fiesta, pero esto también funcionó para mí. ¡Gracias!
stellarossa
44
¿Está esto definido solo para JSF 1.2 o JSF 2 también?
SRy
17
Esto dejará de generar la excepción cuando la vista haya expirado y simplemente continúe la solicitud, pero JSF aún no podrá restaurar el estado de la vista ni encontrar ningún bean de ámbito de vista asociado. Esta transacción se comportará como JSF sin estado y necesitaría restaurar el estado de vista usted mismo en función de los parámetros de solicitud POST para evitar "wtf?" La experiencia del usuario final al procesar el envío del formulario respondió inesperadamente. Si desea aplicar esto solo en páginas JSF específicas, utilice OmniFaces en <o:enableRestorableView>lugar de un parámetro de contexto amplio de la aplicación.
BalusC
5

Primero, lo que debe hacer, antes de cambiar web.xml, es asegurarse de que su ManagedBean implements Serializable:

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

Especialmente si usas MyFaces

ZuzEL
fuente
3

Evite formularios de varias partes en Richfaces:

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

Si está utilizando Richfaces, he descubierto que las solicitudes ajax dentro de los formularios multiparte devuelven una nueva ID de vista en cada solicitud.

Cómo depurar:

En cada solicitud ajax se devuelve una ID de vista, eso está bien siempre que la ID de vista sea siempre la misma. Si obtiene una nueva ID de vista en cada solicitud, entonces hay un problema y debe solucionarse.

tak3shi
fuente
Tenga cuidado al jugar con encuestas, puede evitar que las sesiones de sus usuarios expiren.
Jonathan Simas
0

Podrías usar tu propio AjaxExceptionHandler o primefaces-extensiones

Actualiza tus faces-config.xml

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

Agregue el siguiente código en su página jsf

...
<pe:ajaxErrorHandler />
...
myset
fuente
0

Recibí este error: javax.faces.application.ViewExpiredException. Cuando utilicé diferentes solicitudes, encontré las que tenían el mismo JsessionId, incluso después de reiniciar el servidor. Esto se debe a la memoria caché del navegador. Simplemente cierre el navegador e intente, funcionará.

rahul
fuente
0

Cuando nuestra página está inactiva durante x cantidad de tiempo, la vista caducará y arrojará javax.faces.application.ViewExpiredException para evitar que esto suceda, una solución es crear CustomViewHandler que extienda ViewHandler y anule el método restoreView, todos los demás métodos se delegan al Padre

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

}   

Luego debe agregarlo a su faces-config.xml

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

Gracias por la respuesta original en el siguiente enlace: http://www.gregbugaj.com/?p=164

usuario4924157
fuente
Este enfoque no restaura los beans de vista.
BalusC
-2

Agregue esta línea en su web.xml. Funciona para mí.

<context-param>
        <param-name>org.ajax4jsf.handleViewExpiredOnClient</param-name> 
        <param-value>true</param-value>     
    </context-param>
harshal
fuente
44
Su respuesta será más útil si incluye una explicación y contexto sobre lo que está haciendo el código.
Palpatim
1
Incluso si esta es la respuesta correcta, StackOverflow desalienta respuestas como esta sin una explicación. Sería útil para la comunidad agregar más información sobre por qué funciona.
RacerNerd
-2

Me encontré con este problema yo mismo y me di cuenta de que se debía a un efecto secundario de un filtro que estaba creando y que filtraba todas las solicitudes en la aplicación. Tan pronto como modifiqué el filtro para seleccionar solo ciertas solicitudes, este problema no ocurrió. Tal vez sea bueno verificar dichos filtros en su aplicación y ver cómo se comportan.

javshak
fuente
-3

Agregué la siguiente configuración a web.xml y se resolvió.

<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>
Mohamed Ali
fuente
66
Terrible consejo. Solo haga esto cuando tenga mucha memoria y sus usuarios finales tengan realmente abiertas hasta 500 pestañas del navegador y presione el botón Atrás del navegador hasta 500 veces para las devoluciones de datos síncronas anteriores.
BalusC