Introducción
Se ViewExpiredException
lanzará cada vez que javax.faces.STATE_SAVING_METHOD
se 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.ViewState
de 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.ViewState
campo de entrada oculto contiene en su lugar todo el estado de la vista serializada, por lo que el usuario final no recibirá un mensaje ViewExpiredException
cuando 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 ViewExpiredException
cuando, 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.ViewState
campo 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=true
al outcome
valor.
<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 Filter
que esté asignada en el nombre del servlet del FacesServlet
y 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 ViewExpiredException
actualizació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 ViewExpiredException
actualizació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 ViewExpiredException
despué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-page
para que, en web.xml
lo 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 0
de content
representa la cantidad de segundos antes de redirección, 0
por lo tanto significa "redirigir inmediatamente", se puede utilizar por ejemplo 3
para 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 500
lugar de, <exception-type>
por ejemplo , java.lang.Exception
o java.lang.Throwable
, de lo contrario, todas las excepciones envueltas en ServletException
tal como ViewExpiredException
terminarí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 transient
atributo de <f:view>
a true
:
<f:view transient="true">
</f:view>
De esta forma, el javax.faces.ViewState
campo 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
, readonly
o disabled
atributos 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