Einführung
Das ViewExpiredException
wird ausgelöst, sobald die javax.faces.STATE_SAVING_METHOD
eingestellt ist server
(Standard) und die Enduser sendet eine HTTP POST - Anforderung auf einen Blick über <h:form>
mit <h:commandLink>
, <h:commandButton>
oder <f:ajax>
, während der zugehörige Anzeigestatus nicht mehr zur Verfügung in der Sitzung ist.
Der Ansichtsstatus wird als Wert eines ausgeblendeten Eingabefeldes javax.faces.ViewState
des identifiziert <h:form>
. Wenn die Methode zum Speichern des Status auf festgelegt ist server
, enthält diese nur die Ansichtsstatus-ID, die auf einen serialisierten Ansichtsstatus in der Sitzung verweist. Wenn die Sitzung aus irgendeinem Grund abgelaufen ist (entweder auf Server- oder Clientseite abgelaufen oder das Sitzungscookie aus irgendeinem Grund nicht mehr im Browser oder durch Aufrufen HttpSession#invalidate()
des Servers oder aufgrund eines serverspezifischen Fehlers mit Sitzungscookies als verwaltet wird bekannt in WildFly ), dann ist der Status der serialisierten Ansicht in der Sitzung nicht mehr verfügbar und der Endbenutzer erhält diese Ausnahme. Informationen zum Verständnis der Funktionsweise der Sitzung finden Sie unter Wie funktionieren Servlets? Instanziierung, Sitzungen, gemeinsam genutzte Variablen und Multithreading .
Die Anzahl der Ansichten, die JSF in der Sitzung speichert, ist begrenzt. Wenn das Limit erreicht ist, ist die zuletzt verwendete Ansicht abgelaufen. Siehe auch com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews .
Wenn die Methode zum Speichern des Status auf festgelegt ist client
, enthält das javax.faces.ViewState
ausgeblendete Eingabefeld stattdessen den gesamten Status der serialisierten Ansicht, sodass der Endbenutzer nach ViewExpiredException
Ablauf der Sitzung keine erhält . Dies kann jedoch weiterhin in einer Clusterumgebung auftreten ("FEHLER: MAC wurde nicht überprüft" ist symptomatisch) und / oder wenn ein implementierungsspezifisches Zeitlimit für den clientseitigen Status konfiguriert ist und / oder wenn der Server den AES-Schlüssel während des Neustarts neu generiert Siehe auch Abrufen von ViewExpiredException in einer Clusterumgebung, während die Statusspeichermethode auf Client festgelegt ist und die Benutzersitzung gültig ist , um sie zu lösen.
Stellen Sie unabhängig von der Lösung sicher, dass Sie nicht verwenden enableRestoreView11Compatibility
. Der ursprüngliche Ansichtsstatus wird überhaupt nicht wiederhergestellt. Grundsätzlich werden die Ansicht und alle zugehörigen Beans mit Ansichtsbereich von Grund auf neu erstellt, wodurch alle Originaldaten (Status) verloren gehen. Da sich die Anwendung verwirrend verhält ("Hey, wo sind meine Eingabewerte .. ??"), ist dies sehr schlecht für die Benutzererfahrung. Verwenden <o:enableRestorableView>
Sie besser zustandslose Ansichten oder verwenden Sie sie stattdessen nur für eine bestimmte Ansicht anstatt für alle Ansichten.
Informationen dazu, warum JSF den Ansichtsstatus speichern muss, finden Sie in der folgenden Antwort: Warum speichert JSF den Status von UI-Komponenten auf dem Server?
Vermeiden von ViewExpiredException bei der Seitennavigation
Um zu vermeiden, ViewExpiredException
dass z. B. nach dem Abmelden zurück navigiert wird, wenn die server
Statusspeicherung auf gesetzt ist, reicht es nicht aus, nur die POST-Anforderung nach dem Abmelden umzuleiten. Sie müssen den Browser auch anweisen, die dynamischen JSF-Seiten nicht zwischenzuspeichern. Andernfalls zeigt der Browser sie möglicherweise aus dem Cache an, anstatt eine neue vom Server anzufordern, wenn Sie eine GET-Anforderung darauf senden (z. B. über die Schaltfläche "Zurück").
Das javax.faces.ViewState
ausgeblendete Feld der zwischengespeicherten Seite enthält möglicherweise einen Ansichtsstatus-ID-Wert, der in der aktuellen Sitzung nicht mehr gültig ist. Wenn Sie (ab) POST (Befehlslinks / Schaltflächen) anstelle von GET (reguläre Links / Schaltflächen) für die Navigation von Seite zu Seite verwenden und auf einen solchen Befehlslink / eine solche Befehlsschaltfläche auf der zwischengespeicherten Seite klicken, wird dies wiederum durchgeführt scheitern mit a ViewExpiredException
.
Um eine Umleitung nach dem Abmelden in JSF 2.0 auszulösen, fügen Sie entweder <redirect />
das <navigation-case>
betreffende Element (falls vorhanden) oder ?faces-redirect=true
den outcome
Wert hinzu.
<h:commandButton value="Logout" action="logout?faces-redirect=true" />
oder
public String logout() {
// ...
return "index?faces-redirect=true";
}
Um den Browser anzuweisen, die dynamischen JSF-Seiten nicht zwischenzuspeichern, erstellen Sie eine, Filter
die dem Servlet-Namen des zugeordnet ist, FacesServlet
und fügen Sie die erforderlichen Antwortheader hinzu, um den Browser-Cache zu deaktivieren. Z.B
@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);
}
// ...
}
Vermeiden von ViewExpiredException bei Seitenaktualisierung
Um zu vermeiden, dass ViewExpiredException
beim Aktualisieren der aktuellen Seite das Speichern des Status aktiviert ist server
, müssen Sie nicht nur sicherstellen, dass Sie die Navigation von Seite zu Seite ausschließlich über GET (reguläre Links / Schaltflächen) durchführen, sondern auch sicherstellen dass Sie ausschließlich Ajax verwenden, um die Formulare einzureichen. Wenn Sie das Formular ohnehin synchron senden (nicht Ajax), sollten Sie die Ansicht entweder zustandslos machen (siehe späterer Abschnitt) oder eine Weiterleitung nach dem POST senden (siehe vorherigen Abschnitt).
Eine ViewExpiredException
On-Page-Aktualisierung ist in der Standardkonfiguration ein sehr seltener Fall. Dies kann nur passieren, wenn das Limit für die Anzahl der Ansichten, die JSF in der Sitzung speichert, erreicht wird. Es wird also nur passieren, wenn Sie diesen Grenzwert manuell viel zu niedrig eingestellt haben oder wenn Sie kontinuierlich neue Ansichten im "Hintergrund" erstellen (z. B. durch eine schlecht implementierte Ajax-Umfrage auf derselben Seite oder durch eine schlecht implementierte 404 Fehlerseite bei fehlerhaften Bildern derselben Seite). Siehe auch com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews für Details zu dieser Grenze. Eine andere Ursache ist, dass doppelte JSF-Bibliotheken im Laufzeitklassenpfad in Konflikt miteinander stehen. Das richtige Verfahren zum Installieren von JSF finden Sie auf unserer JSF-Wiki-Seite .
ViewExpiredException behandeln
Wenn Sie eine unvermeidbare behandeln möchten ViewExpiredException
nach einer POST Aktion auf einer beliebigen Seite , die bereits in einigen Browser - Tab / Fenster geöffnet wurde , während Sie in einem anderen Tab / Fenster abgemeldet, dann möchten Sie eine angeben , error-page
für die in web.xml
dem geht auf eine Seite "Ihre Sitzung ist abgelaufen". Z.B
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/WEB-INF/errorpages/expired.xhtml</location>
</error-page>
Verwenden Sie bei Bedarf einen Meta-Refresh-Header auf der Fehlerseite, falls Sie tatsächlich weiter zur Startseite oder Anmeldeseite umleiten möchten.
<!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>
(Das 0
In content
stellt die Anzahl der Sekunden vor der Umleitung dar 0
und bedeutet somit "Sofort umleiten". Sie können z. B. verwenden 3
, um den Browser 3 Sekunden mit der Umleitung warten zu lassen.)
Beachten Sie, dass für die Behandlung von Ausnahmen bei Ajax-Anforderungen eine spezielle Anforderung erforderlich ist ExceptionHandler
. Siehe auch Sitzungszeitlimit und ViewExpiredException-Behandlung für JSF / PrimeFaces-Ajax-Anforderungen . Ein Live-Beispiel finden Sie auf der OmniFaces- FullAjaxExceptionHandler
Präsentationsseite (dies gilt auch für Nicht-Ajax-Anfragen).
Beachten Sie auch , dass Ihre „allgemeine“ Fehlerseite sollte auf kartiert werden <error-code>
von 500
anstelle eines <exception-type>
von zBjava.lang.Exception
oder java.lang.Throwable
, sonst alle Ausnahmen in gewickelt ServletException
als solche ViewExpiredException
würde am Ende noch in der allgemeinen Fehlerseite. Siehe auch ViewExpiredException in java.lang.Throwable-Fehlerseite in web.xml .
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/errorpages/general.xhtml</location>
</error-page>
Staatenlose Ansichten
Eine völlig andere Alternative besteht darin, JSF-Ansichten im zustandslosen Modus auszuführen. Auf diese Weise wird nichts vom JSF-Status gespeichert und die Ansichten verfallen nie, sondern werden bei jeder Anforderung von Grund auf neu erstellt. Sie können , indem das auf stateless Ansichten drehen transient
Attribut <f:view>
zu true
:
<f:view transient="true">
</f:view>
Auf diese Weise die javax.faces.ViewState
erhält ausgeblendete Feld "stateless"
in Mojarra einen festen Wert von (habe MyFaces zu diesem Zeitpunkt noch nicht überprüft). Beachten Sie, dass diese Funktion wurde eingeführt in Mojarra 2.1.19 und 2.2.0 und ist in älteren Versionen nicht verfügbar.
Die Folge ist, dass Sie keine Bohnen mit Sichtbereich mehr verwenden können. Sie verhalten sich jetzt wie Bohnen mit Anforderungsbereich. Einer der Nachteile ist, dass Sie den Status selbst verfolgen müssen, indem Sie mit versteckten Eingaben und / oder losen Anforderungsparametern herumspielen. Hauptsächlich diejenigen Formen mit Eingabefeldern mit rendered
, readonly
oder disabled
betroffenen Attribute , die durch ajax Ereignisse gesteuert werden.
Beachten Sie, dass das <f:view>
nicht unbedingt in der gesamten Ansicht eindeutig sein und / oder sich nur in der Master-Vorlage befinden muss. Es ist auch völlig legitim, es neu zu deklarieren und in einem Vorlagenclient zu verschachteln. Es "erweitert" im Grunde die Eltern<f:view>
dann im . ZB in der Master-Vorlage:
<f:view contentType="text/html">
<ui:insert name="content" />
</f:view>
und im Vorlagenclient:
<ui:define name="content">
<f:view transient="true">
<h:form>...</h:form>
</f:view>
</f:view>
Sie können das sogar <f:view>
in ein einwickeln <c:if>
, um es bedingt zu machen. Beachten Sie, dass es auf die gelten würde gesamte Ansicht gilt, nicht nur für den verschachtelten Inhalt, wie <h:form>
im obigen Beispiel.
Siehe auch
Unabhängig vom konkreten Problem ist die Verwendung von HTTP POST für die reine Navigation von Seite zu Seite nicht sehr benutzer- / SEO-freundlich. In JSF 2.0 sollten Sie wirklich bevorzugen <h:link>
oder<h:button>
die über <h:commandXxx>
diejenigen für Plain - Vanilla - Seite-zu-Seite - Navigation.
Also statt z
<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>
besser machen
<h:link value="Foo" outcome="foo" />
<h:link value="Bar" outcome="bar" />
<h:link value="Baz" outcome="baz" />
Siehe auch