javax.faces.application.ViewExpiredException: a exibição não pôde ser restaurada

175

Eu escrevi aplicativos simples com segurança gerenciada por contêiner. O problema é que quando faço login e abro outra página na qual saio, volto à primeira página e clico em qualquer link etc. ou atualizo a página e recebo essa exceção. Eu acho que é normal (ou talvez não :)) porque eu saí e a sessão foi destruída. O que devo fazer para redirecionar o usuário para, por exemplo, index.xhtml ou login.xhtml e evitar que ele veja a página / mensagem de erro?

Em outras palavras, como posso redirecionar automaticamente outras páginas para a página de índice / login depois que eu sair?

Aqui 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)
l245c4l
fonte

Respostas:

353

Introdução

O ViewExpiredExceptionserá lançada sempre que o javax.faces.STATE_SAVING_METHODestá definido para server(padrão) e o usuário final envia uma solicitação HTTP POST em uma visão via <h:form>com <h:commandLink>, <h:commandButton>ou <f:ajax>, quando o estado de exibição associado não está disponível na sessão anymore.

O estado da visualização é identificado como o valor de um campo javax.faces.ViewStatede entrada oculto do <h:form>. Com o método de economia de estado definido como server, ele contém apenas o ID do estado de exibição que faz referência a um estado de exibição serializado na sessão. Portanto, quando a sessão expirar por algum motivo (excedeu o tempo limite no servidor ou no cliente, ou o cookie da sessão não é mais mantido por algum motivo no navegador, ou chamando HttpSession#invalidate()no servidor, ou devido a um erro específico do servidor com cookies da sessão como conhecido no WildFly ), o estado de exibição serializado não estará mais disponível na sessão e o usuário final receberá essa exceção. Para entender o funcionamento da sessão, consulte também Como os servlets funcionam? Instanciação, sessões, variáveis ​​compartilhadas e multithreading .

Há também um limite na quantidade de visualizações que o JSF armazenará na sessão. Quando o limite for atingido, a exibição usada menos recentemente expirará. Consulte também com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews .

Com o método de economia de estado definido como client, o javax.faces.ViewStatecampo de entrada oculto contém, em vez disso, todo o estado de exibição serializado, para que o usuário final não receba um ViewExpiredExceptionquando a sessão expirar. No entanto, isso ainda pode acontecer em um ambiente de cluster ("ERRO: O MAC não verificou" é sintomático) e / ou quando há um tempo limite específico da implementação no estado do lado do cliente configurado e / ou quando o servidor gera novamente a chave AES durante a reinicialização , consulte também Obtendo ViewExpiredException no ambiente em cluster enquanto o método de economia de estado estiver definido como cliente e a sessão do usuário for válida como resolvê-lo.

Independentemente da solução, certifique-se de não usá-lo enableRestoreView11Compatibility. de maneira alguma restaura o estado original da visualização. Basicamente, recria a visualização e todos os beans com escopo associado à visualização do zero, perdendo assim todos os dados originais (estado). Como o aplicativo se comportará de maneira confusa ("Ei, onde estão meus valores de entrada ... ??"), isso é muito ruim para a experiência do usuário. É melhor usar visualizações sem estado ou, em <o:enableRestorableView>vez disso, para que você possa gerenciá-las apenas em uma visualização específica, em vez de em todas as visualizações.

Quanto ao motivo pelo qual o JSF precisa salvar o estado de exibição, siga esta resposta: Por que o JSF salva o estado dos componentes da interface do usuário no servidor?

Evitando ViewExpiredException na navegação da página

Para evitar ViewExpiredExceptionquando, por exemplo, navegar de volta após o logout quando o salvamento do estado estiver definido como server, apenas o redirecionamento da solicitação POST após o logout não é suficiente. Você também precisa instruir o navegador a não armazenar em cache as páginas dinâmicas do JSF; caso contrário, o navegador poderá mostrá-las a partir do cache, em vez de solicitar uma nova ao servidor quando você enviar uma solicitação GET (por exemplo, com o botão Voltar).

O javax.faces.ViewStatecampo oculto da página em cache pode conter um valor de ID do estado de exibição que não é mais válido na sessão atual. Se você estiver (ab) usando POST (links / botões de comando) em vez de GET (links / botões regulares) para a navegação de página a página e clique nesse link / botão de comando na página em cache, isso por sua vez falhar com um ViewExpiredException.

Para disparar um redirecionamento após o logout no JSF 2.0, adicione <redirect />ao <navigation-case>em questão (se houver) ou ?faces-redirect=trueao outcomevalor.

<h:commandButton value="Logout" action="logout?faces-redirect=true" />

ou

public String logout() {
    // ...
    return "index?faces-redirect=true";
}

Para instruir o navegador a não armazenar em cache as páginas JSF dinâmicas, crie uma Filterque seja mapeada no nome do servlet FacesServlete inclua os cabeçalhos de resposta necessários para desativar o cache do navegador. Por exemplo

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

    // ...
}

Evitando ViewExpiredException na atualização da página

Para evitar ViewExpiredExceptionao atualizar a página atual quando o salvamento de estado está definido como server, você não apenas precisa garantir que está executando a navegação de página a página exclusivamente pelo GET (links / botões regulares), mas também que você está usando exclusivamente ajax para enviar os formulários. Se você estiver enviando o formulário de forma síncrona (sem ser ajax), é melhor tornar a exibição sem estado (consulte a seção posterior) ou enviar um redirecionamento após o POST (consulte a seção anterior).

Ter uma ViewExpiredExceptionatualização na página é na configuração padrão um caso muito raro. Isso só pode acontecer quando o limite da quantidade de visualizações que o JSF armazenará na sessão for atingido. Portanto, isso só acontecerá quando você definir manualmente esse limite muito baixo ou criar continuamente novas visualizações no "plano de fundo" (por exemplo, por uma pesquisa ajax mal implementada na mesma página ou por uma 404 incorretamente implementada página de erro em imagens quebradas da mesma página). Consulte também com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews para obter detalhes sobre esse limite. Outra causa é ter bibliotecas JSF duplicadas no caminho de classe do tempo de execução em conflito entre si. O procedimento correto para instalar o JSF está descrito em nossa página wiki do JSF .

Manipulando ViewExpiredException

Quando você deseja lidar com um inevitável ViewExpiredExceptionapós uma ação POST em uma página arbitrária que já foi aberta em alguma guia / janela do navegador enquanto você está desconectado em outra guia / janela, então você deseja especificar um error-pagepara o web.xmlque se passa para uma página "Sua sessão expirou". Por exemplo

<error-page>
    <exception-type>javax.faces.application.ViewExpiredException</exception-type>
    <location>/WEB-INF/errorpages/expired.xhtml</location>
</error-page>

Use, se necessário, um cabeçalho de meta atualização na página de erro, caso pretenda realmente redirecionar ainda mais para a página inicial ou para a página de login.

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

(o 0in contentrepresenta a quantidade de segundos antes do redirecionamento, 0portanto significa "redirecionar imediatamente", você pode usar, por exemplo, 3para deixar o navegador aguardar 3 segundos com o redirecionamento)

Observe que o tratamento de exceções durante solicitações de ajax requer um especial ExceptionHandler. Consulte também Tempo limite da sessão e manipulação de ViewExpiredException na solicitação ajax do JSF / PrimeFaces . Você pode encontrar um exemplo ao vivo na página de exibição do OmniFacesFullAjaxExceptionHandler (isso também abrange solicitações não-ajax).

Observe também que a sua página de erro "geral" deve ser mapeado em <error-code>de 500vez de um <exception-type>de, por exemplo java.lang.Exception, ou java.lang.Throwable, caso contrário, todas as exceções envolto em ServletExceptioncomo ViewExpiredExceptionainda iria acabar na página de erro geral. Consulte também ViewExpiredException mostrado em java.lang.Throwable error-page em web.xml .

<error-page>
    <error-code>500</error-code>
    <location>/WEB-INF/errorpages/general.xhtml</location>
</error-page>

Visualizações sem estado

Uma alternativa completamente diferente é executar visualizações JSF no modo sem estado. Dessa forma, nada do estado JSF será salvo e as visualizações nunca expirarão, mas serão reconstruídas do zero a cada solicitação. Você pode ativar as visualizações sem estado, configurando o transientatributo de <f:view>para true:

<f:view transient="true">

</f:view>

Dessa forma, o javax.faces.ViewStatecampo oculto terá um valor fixo "stateless"em Mojarra (não verificou o MyFaces neste momento). Observe que esse recurso foi introduzido no Mojarra 2.1.19 e 2.2.0 e não está disponível nas versões mais antigas.

A conseqüência é que você não pode mais usar os beans de escopo de exibição. Agora eles se comportam como feijões com escopo definido por solicitação. Uma das desvantagens é que você mesmo deve rastrear o estado mexendo nas entradas ocultas e / ou nos parâmetros de solicitação perdidos. Principalmente as formas com campos de entrada com rendered, readonlyou disabledatributos que são controlados por eventos Ajax irá ser afectada.

Observe que o <f:view>arquivo não precisa necessariamente ser exclusivo em toda a visualização e / ou residir apenas no modelo mestre. Também é totalmente legítimo redeclará-lo e aninhá-lo em um cliente de modelo. Basicamente "estende" o pai <f:view>então. Por exemplo, no modelo mestre:

<f:view contentType="text/html">
    <ui:insert name="content" />
</f:view>

e no cliente modelo:

<ui:define name="content">
    <f:view transient="true">
        <h:form>...</h:form>
    </f:view>
</f:view>

Você pode até envolver o <f:view>em a <c:if>para torná-lo condicional. Observe que isso se aplicaria a toda a exibição, não apenas ao conteúdo aninhado, como <h:form>no exemplo acima.

Veja também


Independentemente do problema concreto, o uso do HTTP POST para navegação pura de página a página não é muito amigável para o usuário / SEO. No JSF 2.0, você realmente deve preferir <h:link>ou <h:button>substituir os da <h:commandXxx>navegação simples de página a página de baunilha.

Então, ao invés de por exemplo

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

melhor fazer

<h:link value="Foo" outcome="foo" />
<h:link value="Bar" outcome="bar" />
<h:link value="Baz" outcome="baz" />

Veja também

BalusC
fonte
Como posso fazer isso com navegação implícita no java ee 6? Eu não uso faces-config.
L245c4l
1
Ah, você está usando JSF 2.0? Você deveria ter mencionado isso na sua pergunta! Adicione ?faces-redirect=trueao outcome. Atualizei a resposta de acordo.
BalusC
Sim, comecei com java ee :) e estou usando faces-redirect = true em todas as minhas navegações. Uso h: commandLink apenas quando tenho ações associadas a ele. Por exemplo, link Logout ... Eu tenho a ação String logout () onde invalido a sessão e redireciono para o login, mas ele não funciona na página em que eu estava logado e, no momento, desconectado e lança essa exceção :(
l245c4l
1
Mais uma vez obrigado e desculpe pelo que :) mas pelo menos eu tenho resposta rápida e profissional em nenhum momento: p
l245c4l
1
@LS: no entanto, o filtro ainda é obrigatório para o caso sempre que um botão é pressionado após um POST expirado e tenta invocar outra solicitação POST nele. Caso contrário, isso resultaria de maneira não intuitiva nessa exceção.
BalusC
56

Você já tentou adicionar linhas abaixo ao seu web.xml?

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

Achei isso muito eficaz quando encontrei esse problema.

Mike GH
fonte
1
funcionou para mim também. Obrigado pela resposta. Qual é o propósito disso?
287 MartK
2
Não me lembro exatamente, mas encontrei esta solução no site do ICEFaces.
Mike GH
posso estar um pouco atrasado para a festa, mas isso funcionou para mim também. obrigado!
stellarossa
4
Isso também está definido apenas para o JSF 1.2 ou JSF 2?
SRY
17
Isso irá parar de lançar a exceção quando a visualização expirar e simplesmente continuará a solicitação, mas o JSF ainda não poderá restaurar o estado da visualização nem encontrar nenhum bean no escopo da visualização associada. Essa transação se comportará como um JSF sem estado e você precisará restaurar o estado da exibição com base nos parâmetros de solicitação POST para evitar "wtf?" A experiência do usuário final ao processar o envio do formulário respondeu inesperadamente. Se você deseja aplicar isso apenas em páginas JSF específicas, use o OmniFaces em <o:enableRestorableView>vez de um parâmetro de contexto amplo do aplicativo.
BalusC
5

Primeiro, o que você deve fazer antes de alterar o web.xml é garantir que seu ManagedBean implements Serializable:

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

Especialmente se você usar o MyFaces

ZuzEL
fonte
3

Evite formulários de várias partes no Richfaces:

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

Se você estiver usando Richfaces, descobri que solicitações de ajax dentro de formulários de várias partes retornam um novo ID de exibição em cada solicitação.

Como depurar:

Em cada solicitação ajax, um ID de exibição é retornado, o que é bom, desde que o ID de exibição seja sempre o mesmo. Se você receber um novo ID de exibição em cada solicitação, há um problema e deve ser corrigido.

tak3shi
fonte
Tenha cuidado quando se joga com as pesquisas, pode impedir que as suas sessões de usuários para expirar ..
Jonathan Simas
0

Você pode usar seu próprio AjaxExceptionHandler ou extensões primefaces

Atualize seu faces-config.xml

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

Adicione o seguinte código na sua página jsf

...
<pe:ajaxErrorHandler />
...
myset
fonte
0

Eu estava recebendo este erro: javax.faces.application.ViewExpiredException.Quando usando solicitações diferentes, encontrei as que tinham o mesmo JsessionId, mesmo depois de reiniciar o servidor. Portanto, isso ocorre devido ao cache do navegador. Basta fechar o navegador e tentar, ele funcionará.

rahul
fonte
0

Quando nossa página fica inativa por x tempo, a exibição expira e lança javax.faces.application.ViewExpiredException para impedir que isso aconteça. Uma solução é criar CustomViewHandler que estende o ViewHandler e substitui o método restoreView, todos os outros métodos estão sendo delegados no método Pai

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

}   

Então você precisa adicioná-lo ao seu faces-config.xml

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

Obrigado pela resposta original no link abaixo: http://www.gregbugaj.com/?p=164

user4924157
fonte
Essa abordagem não restaura os beans com escopo definido na exibição.
BalusC
-2

Por favor, adicione esta linha no seu web.xml Funciona para mim

<context-param>
        <param-name>org.ajax4jsf.handleViewExpiredOnClient</param-name> 
        <param-value>true</param-value>     
    </context-param>
harshal
fonte
4
Sua resposta será mais útil se você incluir explicações e contexto sobre o que o código está fazendo.
Palpatim
1
Mesmo se essa for a resposta correta, o StackOverflow desencoraja respostas como essa sem uma explicação. Seria útil para a comunidade adicionar mais informações sobre por que ela funciona.
RacerNerd
-2

Eu me deparei com esse problema e percebi que era por causa do efeito colateral de um Filtro que eu criei que estava filtrando todas as solicitações no aplicativo. Assim que modifiquei o filtro para selecionar apenas determinadas solicitações, esse problema não ocorreu. Talvez seja bom verificar esses filtros em seu aplicativo e ver como eles se comportam.

javshak
fonte
-3

Eu adiciono a seguinte configuração ao web.xml e ela foi resolvida.

<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>
Mohamed Ali
fonte
6
Terrível conselho. Faça isso somente quando você tiver bastante memória e seus usuários finais tiverem consistentemente até 500 guias do navegador abertas e pressione o botão Voltar do navegador até 500 vezes para postagens síncronas anteriores.
BalusC