jsf - viewid - javax.faces.application.ViewExpiredException: la vista no se pudo restaurar
view expired primefaces (10)
Introducción
La ViewExpiredException
se ViewExpiredException
siempre que javax.faces.STATE_SAVING_METHOD
se establezca en server
(valor predeterminado) y el usuario final envíe una solicitud HTTP POST en una vista a través de <h:form>
con <h:commandLink>
, <h:commandButton>
o <f:ajax>
, mientras que el estado de vista asociado ya no está disponible en la sesión.
El estado de vista se identifica como el valor de un campo de entrada oculto javax.faces.ViewState
de <h:form>
. Con el método de ahorro de estado configurado en server
, este contiene solo el ID de estado de vista que hace referencia a un estado de vista serializada en la sesión. Entonces, cuando la sesión expira por algún motivo (ya sea que haya agotado el tiempo en el servidor o cliente, o la cookie de sesión ya no se mantiene por algún motivo en el navegador, o llamando a HttpSession#invalidate()
en el servidor o debido a un servidor específico error con las cookies de sesión como se conoce en WildFly ), entonces el estado de 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 multihebra .
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 el client
, el campo de entrada oculto javax.faces.ViewState
contiene en su lugar el estado de vista serializado completo, por lo que el usuario final no obtendrá una ViewExpiredException
cuando ViewExpiredException
la sesión. Sin embargo, todavía puede suceder en un entorno de clúster ("ERROR: MAC no verificó" es sintomático) y / o cuando hay un tiempo de espera específico de la implementación en el estado del cliente configurado y / o cuando el servidor vuelve a generar la clave AES durante el reinicio , consulte también Obtener ViewExpiredException en un entorno agrupado, mientras que 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 utilizar enableRestoreView11Compatibility
. no restaura en absoluto el estado original de la vista. Básicamente, recrea la vista y todos los beans de ámbito de vista asociado 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 <o:enableRestorableView>
lugar para que pueda administrarlo en una vista específica solamente en lugar de en todas las vistas.
En cuanto a por qué JSF necesita guardar el estado de la 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 del cierre de sesión cuando el ahorro de estado se establece en el server
, solo redirigir la solicitud POST después del cierre de 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 desde el servidor cuando envíe una solicitud GET (por ejemplo, mediante el botón Atrás).
El campo oculto javax.faces.ViewState
de la página almacenada en caché puede contener un valor de ID de estado de vista que ya no es válido en la sesión actual. Si utiliza (ab) POST (enlaces / botones de comando) en lugar de GET (enlaces / botones regulares) para la navegación de página a página, y hace clic en dicho enlace o botón de comando en la página almacenada en caché, esto a su vez fallar con una ViewExpiredException
.
Para activar un redireccionamiento después de cerrar la sesión en JSF 2.0, agregue <redirect />
al <navigation-case>
en cuestión (si existe), o agregue ?faces-redirect=true
al valor de outcome
.
<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 un Filter
que esté asignado al nombre del servlet de FacesServlet
y agregue los encabezados de respuesta necesarios para deshabilitar el 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 página de actualización
Para evitar ViewExpiredException
al actualizar la página actual cuando el ahorro de estado está configurado como server
, no solo debe asegurarse de realizar la navegación de página a página exclusivamente mediante GET (enlaces / botones regulares), sino que también necesita asegúrese de utilizar exclusivamente ajax para enviar los formularios. Si envía el formulario de forma sincrónica (no ajax) de todos modos, será mejor que la vista sea sin estado (consulte la sección posterior) o que envíe una redirección después del POST (consulte la sección anterior).
Tener una ViewExpiredException
en la actualización de la página está en configuración predeterminada, un caso muy raro. Solo puede suceder cuando se alcanza el límite en la cantidad de vistas que almacenará JSF en la sesión. Por lo tanto, solo ocurrirá cuando haya configurado manualmente ese límite demasiado bajo, o que esté continuamente creando nuevas vistas en el fondo (por ejemplo, mediante una encuesta mal implementada). Consulte también com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews . Otra causa es que las bibliotecas JSF duplicadas en classpath en tiempo de ejecución entran en conflicto entre sí. El procedimiento correcto para instalar JSF se describe en nuestra página wiki de JSF .
Manejo de ViewExpiredException
Cuando desee manejar una inevitable ViewExpiredException
después de una acción POST en una página arbitraria que ya fue abierta en alguna pestaña / ventana del navegador mientras está desconectado en otra pestaña / ventana, entonces desea especificar una error-page
de error-page
para eso en web.xml
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 actualización de meta en la página de error en caso de que tenga la intención de redireccionar aún más a la página de inicio o de 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>
(el 0
en el content
representa la cantidad de segundos antes de redirigir, 0
significa "redirigir inmediatamente", puede usar, por ejemplo, 3
para que el navegador espere 3 segundos con la redirección)
Tenga en cuenta que el manejo de excepciones durante las solicitudes ajax requiere un ExceptionHandler
especial. Consulte también el tiempo de espera de la sesión y el manejo de ViewExpiredException en la solicitud JSF / PrimeFaces ajax . Puede encontrar un ejemplo en vivo en la página de presentación de OmniFaces FullAjaxExceptionHandler
(esto también cubre solicitudes que no son Ajax).
También tenga en cuenta que su página de error "general" debe asignarse a <error-code>
de 500
lugar de a un <exception-type>
de, por ejemplo, java.lang.Exception
o java.lang.Throwable
, de lo contrario, todas las excepciones envueltas en ServletException
como ViewExpiredException
aún terminaría 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 forma, nada del estado de 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 estableciendo el atributo transient
de <f:view>
en true
:
<f:view transient="true">
</f:view>
De esta forma, el campo oculto de javax.faces.ViewState
obtendrá un valor fijo de "stateless"
en Mojarra (no ha marcado MyFaces en este punto). Tenga en cuenta que esta característica se introduced en Mojarra 2.1.19 y 2.2.0 y no está disponible en versiones anteriores.
La consecuencia es que ya no puede usar ver los granos de ámbito. Ahora se comportarán como beans de á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 atributos rendered
, de readonly
o disabled
que están controlados por eventos ajax se verá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 una plantilla cliente. Básicamente, "extiende" el padre <f:view>
continuación. Por ejemplo, en la plantilla maestra:
<f:view contentType="text/html">
<ui:insert name="content" />
</f:view>
y en plantilla cliente:
<ui:define name="content">
<f:view transient="true">
<h:form>...</h:form>
</f:view>
</f:view>
Incluso puede ajustar <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 el <h:form>
en el ejemplo anterior.
Ver también
- ViewExpiredException se muestra en java.lang.Throwable error-page en web.xml
- Compruebe si existe sesión JSF
- Tiempo de espera de la sesión y manejo de ViewExpiredException en la solicitud JSF / PrimeFaces ajax
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 <h:commandXxx>
para 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 hacer
<h:link value="Foo" outcome="foo" />
<h:link value="Bar" outcome="bar" />
<h:link value="Baz" outcome="baz" />
Ver también
He escrito una aplicación sencilla con seguridad administrada por contenedor. El problema es cuando inicio sesión y abro otra página en la que salgo, luego vuelvo a la primera página y hago clic en cualquier enlace, etc. o renuevo la página, obtengo esta excepción. Supongo que es normal (o tal vez no :)) porque cerré la sesión y la sesión se destruye. ¿Qué debo hacer para redirigir al usuario a, por ejemplo, index.xhtml o login.xhtml y salvarlo de ver esa página / mensaje de error?
En otras palabras, ¿cómo puedo redirigir automáticamente otras páginas para indexar / iniciar sesión después de desconectarme?
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)
¿Has intentado agregar líneas a continuación en tu web.xml
?
<context-param>
<param-name>com.sun.faces.enableRestoreView11Compatibility</param-name>
<param-value>true</param-value>
</context-param>
Encontré esto muy efectivo cuando encontré este problema.
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>
Cuando nuestra página está inactiva durante un período de tiempo x, la vista caducará y lanzará javax.faces.application.ViewExpiredException para evitar que esto suceda. Una solución es crear CustomViewHandler que amplíe 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 debes agregarlo a tus 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
Lo primero que debe hacer antes de cambiar a web.xml es asegurarse de que ManagedBean implements Serializable
:
@ManagedBean
@ViewScoped
public class Login implements Serializable {
}
Especialmente si usas MyFaces
Me encontré con este problema por mi cuenta y me di cuenta de que se debía a un efecto secundario de un filtro que creé 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 esos filtros en su aplicación y ver cómo se comportan.
Por favor 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>
Recibí este error: javax.faces.application.ViewExpiredException. Cuando usaba 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. Solo cierre el navegador e intente, funcionará.
Usted podría usar su propio AjaxExceptionHandler o Primefaces-Extensions
Actualiza tu 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 />
...
Evite formularios multiparte en Richfaces:
<h:form enctype="multipart/form-data">
<a4j:poll id="poll" interval="10000"/>
</h:form>
Si está utilizando Richfaces, he encontrado que las solicitudes ajax dentro de formularios de varias partes devuelven una nueva ID de vista en cada solicitud.
Cómo depurar:
En cada solicitud de 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 corregirse.