javax.faces.application.ViewExpiredException: impossibile ripristinare la vista


175

Ho scritto una semplice applicazione con sicurezza gestita dal contenitore. Il problema è quando accedo e apro un'altra pagina in cui esco, poi torno alla prima pagina e faccio clic su qualsiasi collegamento ecc. O aggiorna la pagina ottengo questa eccezione. Immagino sia normale (o forse no :)) perché mi sono disconnesso e la sessione è stata distrutta. Cosa devo fare per reindirizzare l'utente ad esempio index.xhtml o login.xhtml e salvarlo dalla visualizzazione della pagina / messaggio di errore?

In altre parole, come posso reindirizzare automaticamente altre pagine all'indice / alla pagina di accesso dopo essermi disconnesso?

Ecco qui:

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)

Risposte:


353

introduzione

La ViewExpiredExceptionsaranno 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.ViewStatedi 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.ViewStatecampo di input nascosto contiene invece l'intero stato della vista serializzata, quindi l'utente finale non riceverà un messaggio ViewExpiredExceptionalla 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, ViewExpiredExceptionad 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.ViewStatecampo 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=trueal outcomevalore.

<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 Filteroggetto mappato sul nome servlet di FacesServlete 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 ViewExpiredExceptionquando 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 ViewExpiredExceptionaggiornamento 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 ViewExpiredExceptiondopo 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-pageper quello in web.xmlcui 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>

( 0in contentrappresenta la quantità di secondi prima del reindirizzamento, 0quindi significa "reindirizzamento immediato", è possibile utilizzare ad es. 3per 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 500invece di un <exception-type>, ad esempio, java.lang.Exceptiono java.lang.Throwable, in caso contrario tutte le eccezioni avvolti in ServletExceptionquali ViewExpiredExceptionsarebbero 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' transientattributo di <f:view>su true:

<f:view transient="true">

</f:view>

In questo modo il javax.faces.ViewStatecampo 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, readonlyo disabledsaranno 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


Come posso farlo con la navigazione implicita in java ee 6? Non uso face-config.
l245c4l,

1
Oh, stai usando JSF 2.0? Avresti dovuto menzionarlo nella tua domanda! Aggiungi ?faces-redirect=truea outcome. Ho aggiornato la risposta di conseguenza.
BalusC

Sì, ho appena iniziato con Java ee :) e sto usando faces-redirect = true in tutte le mie navigazioni. Uso h: commandLink solo quando ho delle azioni associate. Ad esempio il collegamento di logout ... Ho un'azione String logout () in cui invalido la sessione e reindirizzo al login, ma non funziona sulla pagina in cui sono stato effettuato l'accesso e al momento disconnesso e genera tale eccezione :(
l245c4l

1
Grazie ancora e scusa per quello :) ma almeno ho avuto una risposta rapida e professionale in pochissimo tempo: p
l245c4l

1
@LS: il filtro è comunque obbligatorio per il caso ogni volta che si preme il pulsante Indietro dopo un POST scaduto e si tenta di invocare un'altra richiesta POST su di esso. Ciò comporterebbe inavvertitamente questa eccezione.
BalusC

56

Hai provato ad aggiungere righe di seguito al tuo web.xml?

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

Ho trovato questo molto efficace quando ho riscontrato questo problema.


1
ha funzionato anche per me. Grazie per la risposta. Qual è lo scopo di questo?
MartK,

2
Non ricordo esattamente, ma ho trovato questa soluzione sul sito ICEFaces.
Mike GH,

potrei essere un po 'in ritardo alla festa, ma ha funzionato anche per me. Grazie!
stellarossa,

4
È definito solo per JSF 1.2 o JSF 2?
SRy

17
Questo smetterà di generare l'eccezione quando la vista è scaduta e semplicemente continuerà la richiesta, ma JSF non sarà ancora in grado di ripristinare lo stato della vista né trovare alcun bean con ambito vista associato. Questa transazione si comporterà come JSF senza stato e sarà necessario ripristinare autonomamente lo stato di visualizzazione in base ai parametri di richiesta POST per evitare "wtf?" l'esperienza dell'utente finale durante l'elaborazione del modulo inviato ha risposto in modo imprevisto. Se si desidera applicarlo solo su pagine JSF specifiche, utilizzare <o:enableRestorableView>invece OmniFaces anziché un parametro di contesto a livello di applicazione.
BalusC

5

Innanzitutto, prima di modificare web.xml devi assicurarti che ManagedBean implements Serializable:

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

Soprattutto se usi MyFaces


3

Evita i moduli multipart in Richfaces:

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

Se stai utilizzando Richfaces, ho scoperto che le richieste Ajax all'interno dei moduli multipart restituiscono un nuovo ID visualizzazione su ogni richiesta.

Come eseguire il debug:

Su ogni richiesta Ajax viene restituito un ID vista, che va bene purché l'ID vista sia sempre lo stesso. Se si ottiene un nuovo ID visualizzazione per ogni richiesta, si verifica un problema e deve essere risolto.


Fai attenzione quando giochi con i sondaggi, può impedire la scadenza delle sessioni degli utenti ..
Jonathan Simas,

0

Puoi usare il tuo AjaxExceptionHandler personalizzato o le estensioni primefaces

Aggiorna il tuo volto-config.xml

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

Aggiungi il seguente codice nella tua pagina jsf

...
<pe:ajaxErrorHandler />
...

0

Stavo ottenendo questo errore: javax.faces.application.ViewExpiredException.Quando utilizzo richieste diverse, ho trovato quelle con lo stesso JsessionId, anche dopo aver riavviato il server. Quindi questo è dovuto alla cache del browser. Basta chiudere il browser e provare, funzionerà.


0

Quando la nostra pagina è inattiva per x il tempo necessario, la vista scadrà e genererà javax.faces.application.ViewExpiredException per evitare che ciò accada una soluzione è creare CustomViewHandler che estende ViewHandler e sovrascrivere il metodo restoreView tutti gli altri metodi vengono delegati al Genitore

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

}   

Quindi è necessario aggiungerlo a faces-config.xml

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

Grazie per la risposta originale sul seguente link: http://www.gregbugaj.com/?p=164


Questo approccio non ripristina i bean con ambito di visualizzazione.
BalusC,

-2

Aggiungi questa riga nel tuo web.xml Funziona per me

<context-param>
        <param-name>org.ajax4jsf.handleViewExpiredOnClient</param-name> 
        <param-value>true</param-value>     
    </context-param>

4
La tua risposta sarà più utile se includi spiegazioni e contesto su ciò che sta facendo il codice.
Palpatim,

1
Anche se questa è la risposta corretta StackOverflow scoraggia risposte come questa senza una spiegazione. Sarebbe utile per la comunità aggiungere ulteriori informazioni sul perché funziona.
RacerNerd,

-2

Mi sono imbattuto in questo problema da solo e mi sono reso conto che era a causa di un effetto collaterale di un filtro che ho creato che filtrava tutte le richieste sull'applicazione. Non appena ho modificato il filtro per selezionare solo determinate richieste, questo problema non si è verificato. Potrebbe essere utile controllare tali filtri nella tua applicazione e vedere come si comportano.


-3

Aggiungo la seguente configurazione a web.xml e si è risolto.

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

6
Terribile consiglio. Fallo solo quando hai molta memoria e i tuoi utenti hanno davvero aperto fino a 500 schede del browser e premi il pulsante Indietro del browser fino a 500 volte ai precedenti postback sincroni.
BalusC
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.