introduzione
La ViewExpiredException
saranno gettati ogni volta che il javax.faces.STATE_SAVING_METHOD
è impostato server
(default) e l'utente finale invia una richiesta HTTP POST su una visione via <h:form>
con <h:commandLink>
, <h:commandButton>
o <f:ajax>
, mentre lo stato di visualizzazione associata non è disponibile nella sessione più.
Lo stato della vista viene identificato come valore di un campo javax.faces.ViewState
di input nascosto di <h:form>
. Con il metodo di salvataggio dello stato impostato su server
, questo contiene solo l'ID dello stato di visualizzazione che fa riferimento a uno stato di visualizzazione serializzato nella sessione. Pertanto, quando la sessione è scaduta per qualche motivo (scaduto nel server o lato client o il cookie di sessione non viene più gestito per qualche motivo nel browser o chiamando il HttpSession#invalidate()
server o a causa di un bug specifico del server con cookie di sessione come noto in WildFly ), quindi lo stato della vista serializzata non è più disponibile nella sessione e l'utente finale riceverà questa eccezione. Per comprendere il funzionamento della sessione, vedere anche Come funzionano i servlet? Istantanea, sessioni, variabili condivise e multithreading .
C'è anche un limite alla quantità di visualizzazioni che JSF memorizzerà nella sessione. Quando viene raggiunto il limite, la vista utilizzata meno di recente scadrà. Vedi anche com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews .
Con il metodo di salvataggio dello stato impostato su client
, il javax.faces.ViewState
campo di input nascosto contiene invece l'intero stato della vista serializzata, quindi l'utente finale non riceverà un messaggio ViewExpiredException
alla scadenza della sessione. Tuttavia, può ancora verificarsi in un ambiente cluster ("ERRORE: MAC non verificato" è sintomatico) e / o quando esiste un timeout specifico dell'implementazione nello stato lato client configurato e / o quando il server genera nuovamente la chiave AES durante il riavvio , vedere anche Ottenere ViewExpiredException in un ambiente cluster mentre il metodo di salvataggio dello stato è impostato su client e la sessione utente è valida su come risolverlo.
Indipendentemente dalla soluzione, assicurarsi di non utilizzare enableRestoreView11Compatibility
. non ripristina affatto lo stato di visualizzazione originale. Fondamentalmente ricrea la vista e tutti i bean con ambito vista associati da zero e quindi perde tutti i dati originali (stato). Poiché l'applicazione si comporterà in modo confuso ("Ehi, dove sono i miei valori di input .. ??"), questo è molto negativo per l'esperienza dell'utente. Meglio usare le viste senza stato o <o:enableRestorableView>
invece in modo da poterlo gestire solo su una vista specifica anziché su tutte le viste.
Per quanto riguarda il motivo per cui JSF deve salvare lo stato di visualizzazione, vai a questa risposta: Perché JSF salva lo stato dei componenti dell'interfaccia utente sul server?
Evitare ViewExpiredException nella navigazione della pagina
Per evitare, ViewExpiredException
ad esempio, di tornare indietro dopo il logout quando il salvataggio dello stato è impostato su server
, il reindirizzamento della richiesta POST solo dopo il logout non è sufficiente. È inoltre necessario indicare al browser di non memorizzare nella cache le pagine JSF dinamiche, altrimenti il browser potrebbe mostrarle dalla cache invece di richiederne una nuova dal server quando si invia una richiesta GET su di essa (ad es. Tramite il pulsante Indietro).
Il javax.faces.ViewState
campo nascosto della pagina memorizzata nella cache può contenere un valore ID stato vista che non è più valido nella sessione corrente. Se stai (ab) stai usando POST (link / pulsanti di comando) invece di GET (link / pulsanti regolari) per la navigazione da pagina a pagina e fai clic su tale link / pulsante di comando nella pagina cache, quindi a sua volta fallire con a ViewExpiredException
.
Per attivare un reindirizzamento dopo il logout in JSF 2.0, aggiungere <redirect />
alla <navigation-case>
domanda in questione (se presente) o aggiungere ?faces-redirect=true
al outcome
valore.
<h:commandButton value="Logout" action="logout?faces-redirect=true" />
o
public String logout() {
// ...
return "index?faces-redirect=true";
}
Per indicare al browser di non memorizzare nella cache le pagine JSF dinamiche, creare un Filter
oggetto mappato sul nome servlet di FacesServlet
e aggiungere le intestazioni di risposta necessarie per disabilitare la cache del browser. Per esempio
@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);
}
// ...
}
Evitare ViewExpiredException sull'aggiornamento della pagina
Per evitare ViewExpiredException
quando si aggiorna la pagina corrente quando è impostato il salvataggio dello stato server
, non solo è necessario assicurarsi di eseguire la navigazione da pagina a pagina esclusivamente tramite GET (collegamenti / pulsanti regolari), ma è anche necessario assicurarsi che stai utilizzando esclusivamente Ajax per inviare i moduli. Se si sta inviando il modulo in modo sincrono (non ajax), è consigliabile rendere la vista senza stato (vedere la sezione successiva) o inviare un reindirizzamento dopo POST (vedere la sezione precedente).
Avere un ViewExpiredException
aggiornamento sulla pagina è nella configurazione predefinita un caso molto raro. Può succedere solo quando viene raggiunto il limite sulla quantità di visualizzazioni che JSF memorizzerà nella sessione. Quindi, accadrà solo quando hai impostato manualmente quel limite su un valore troppo basso, o quando crei continuamente nuove viste in "background" (ad esempio da un sondaggio Ajax implementato male nella stessa pagina o da un 404 implementato male pagina di errore sulle immagini rotte della stessa pagina). Vedi anche com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews per dettagli su quel limite. Un'altra causa sta avendo librerie JSF duplicate nel percorso di classe runtime in conflitto tra loro. La procedura corretta per installare JSF è descritta nella nostra pagina wiki JSF .
Gestione di ViewExpiredException
Quando si desidera gestire un inevitabile ViewExpiredException
dopo un'azione POST su una pagina arbitraria che è già stata aperta in una scheda / finestra del browser mentre si è disconnessi in un'altra scheda / finestra, si desidera specificare un error-page
per quello in web.xml
cui va alla pagina "La sessione è scaduta". Per esempio
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/WEB-INF/errorpages/expired.xhtml</location>
</error-page>
Se necessario, utilizzare un'intestazione di meta-aggiornamento nella pagina di errore nel caso in cui si desideri reindirizzare ulteriormente alla pagina principale o alla pagina di accesso.
<!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>
( 0
in content
rappresenta la quantità di secondi prima del reindirizzamento, 0
quindi significa "reindirizzamento immediato", è possibile utilizzare ad es. 3
per consentire al browser di attendere 3 secondi con il reindirizzamento)
Si noti che la gestione delle eccezioni durante le richieste Ajax richiede uno speciale ExceptionHandler
. Vedi anche Timeout sessione e gestione ViewExpiredException su richiesta Ajax JSF / PrimeFaces . Puoi trovare un esempio dal vivo nella pagina della vetrina di OmniFacesFullAjaxExceptionHandler
(che copre anche le richieste non Ajax ).
Si noti inoltre che la tua pagina di errore "generale" dovrebbe essere mappato su <error-code>
di 500
invece di un <exception-type>
, ad esempio, java.lang.Exception
o java.lang.Throwable
, in caso contrario tutte le eccezioni avvolti in ServletException
quali ViewExpiredException
sarebbero ancora finire nella pagina di errore generale. Vedi anche ViewExpiredException mostrato in java.lang.Pagina di errore lanciabile in web.xml .
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/errorpages/general.xhtml</location>
</error-page>
Viste apolidi
Un'alternativa completamente diversa è eseguire le viste JSF in modalità stateless. In questo modo nulla dello stato JSF verrà salvato e le viste non scadranno mai, ma verranno ricostruite da zero su ogni richiesta. È possibile attivare le viste senza stato impostando l' transient
attributo di <f:view>
su true
:
<f:view transient="true">
</f:view>
In questo modo il javax.faces.ViewState
campo nascosto otterrà un valore fisso "stateless"
in Mojarra (non ho verificato MyFaces a questo punto). Questa funzionalità è stata introdotta in Mojarra 2.1.19 e 2.2.0 e non è disponibile nelle versioni precedenti.
La conseguenza è che non è più possibile utilizzare i bean con ambito di visualizzazione. Ora si comporteranno come bean con ambito di richiesta. Uno degli svantaggi è che devi tenere traccia dello stato da solo, manipolando con input nascosti e / o parametri di richiesta allentati. Principalmente quelle forme con campi di input con rendered
, readonly
o disabled
saranno influenzati attributi che sono controllati da eventi ajax.
Si noti che <f:view>
non è necessario che sia necessariamente univoco in tutta la vista e / o risieda solo nel modello principale. È anche completamente legittimo dichiararlo e annidarlo in un client modello. Fondamentalmente "estende" il genitore <f:view>
allora. Ad esempio nel modello principale:
<f:view contentType="text/html">
<ui:insert name="content" />
</f:view>
e nel template client:
<ui:define name="content">
<f:view transient="true">
<h:form>...</h:form>
</f:view>
</f:view>
Puoi anche avvolgere <f:view>
in a <c:if>
per renderlo condizionale. Si noti che si applicherà sull'intera vista, non solo sui contenuti nidificati, come <h:form>
nell'esempio sopra.
Guarda anche
Non correlato al problema concreto, l'utilizzo di HTTP POST per una navigazione da pagina a pagina non è molto user / SEO friendly. In JSF 2.0 dovresti davvero preferire <h:link>
o <h:button>
oltre <h:commandXxx>
quelli per la semplice navigazione da pagina a pagina vaniglia.
Quindi invece di es
<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>
meglio fare
<h:link value="Foo" outcome="foo" />
<h:link value="Bar" outcome="bar" />
<h:link value="Baz" outcome="baz" />
Guarda anche