ti-enxame.com

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

Eu escrevi aplicativo simples com segurança gerenciada por contêiner. O problema é quando eu logar e abrir outra página na qual eu efetuo logout, então eu volto para a primeira página e clico em qualquer link etc ou atualizo a página eu recebo essa exceção. Eu acho que é normal (ou talvez não :)) porque eu fiz o logout 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 salvá-lo de ver a página/mensagem de erro?

Em outras palavras, como posso redirecionar automaticamente outras páginas para a página de índice/login depois de 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)
163
l245c4l

Introdução

O ViewExpiredException será lançado sempre que javax.faces.STATE_SAVING_METHOD for definido como server (default) e o usuário final enviar uma solicitação HTTP POST em uma visão via <h:form> com <h:commandLink>, <h:commandButton> ou <f:ajax>, enquanto o estado de exibição associado não estiver disponível na sessão mais.

O estado de exibição é identificado como o valor de um campo de entrada oculto javax.faces.ViewState do <h:form>. Com o método de salvamento de estado definido como server, isso contém apenas o ID do estado de exibição que faz referência a um estado de exibição serializado na sessão. Assim, quando a sessão expirou por algum motivo (expirou no servidor ou no cliente, ou o cookie de 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 de sessão como é conhecido em WildFly ), o estado de exibição serializado não está mais disponível na sessão e o usuário final receberá esta 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 visualização usada menos recentemente será expirada. Veja também com.Sun.faces.numberOfViewsInSession vs com.Sun.faces.numberOfLogicalViews .

Com o método saving do estado configurado para client, o campo de entrada oculto javax.faces.ViewState contém, em vez disso, todo o estado de visualização serializado, portanto, o usuário final não obterá um ViewExpiredException quando a sessão expirar. No entanto, 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 re-gera a chave AES durante a reinicialização , veja também Obtendo ViewExpiredException no ambiente de cluster enquanto o método de salvamento de estado é definido como cliente e a sessão de usuário é válida como resolvê-lo.

Independentemente da solução, certifique-se de fazer não usar enableRestoreView11Compatibility. ele não restaura o estado de exibição original. Basicamente, ele recria a visão e todos os beans associados ao escopo da visão a partir do zero e assim perde 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. Use melhor as visualizações sem estado ou <o:enableRestorableView> para poder gerenciá-lo em uma visualização específica, em vez de em todas as visualizações.

Quanto ao por que JSF precisa salvar o estado de visão, vá para a seguinte resposta: Por que o JSF salva o estado dos componentes da UI no servidor?

Evitando ViewExpiredException na navegação da página

Para evitar ViewExpiredException quando, por ex. Navegando de volta após o logout quando o salvamento de estado é definido como server, somente redirecionar a solicitação POSTapós o logout não é suficiente. Você também precisa instruir o navegador para não armazenar em cache as páginas JSF dinâmicas, caso contrário, o navegador pode mostrá-las a partir do cache, em vez de solicitar uma nova do servidor quando você envia uma requisição GET (por exemplo, pelo botão Voltar).

O campo oculto javax.faces.ViewState da página em cache pode conter um valor de ID de estado de visualização que não é mais válido na sessão atual. Se você está (ab) usando POST (links/botões de comando) em vez de GET (links/botões regulares) para navegação de página a página, e clique em tal link de comando/botão na página em cache , então 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 adicione ?faces-redirect=true ao valor outcome.

<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 um Filter que é mapeado no nome do servlet de FacesServlet e 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 ViewExpiredException ao atualizar a página atual quando o salvamento de estado está definido como server, você não apenas precisa verificar se está realizando a navegação de página a página exclusivamente por GET (links/botões comuns), mas também precisa Certifique-se de usar exclusivamente o ajax para enviar os formulários. Se você estiver enviando o formulário de forma síncrona (não-ajax) de qualquer maneira, é melhor tornar a exibição sem estado (veja a seção posterior) ou enviar um redirecionamento após POST (consulte a seção anterior).

Ter um ViewExpiredException na atualização da 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 armazena na sessão é atingido. Então, isso só acontecerá quando você definir manualmente esse limite como muito baixo ou se estiver criando continuamente novas exibições no "segundo plano" (por exemplo, por uma pesquisa de ajax mal implementada na mesma página ou por um erro 404 mal implementado). página de erro em imagens quebradas da mesma página). Veja também com.Sun.faces.numberOfViewsInSession vs com.Sun.faces.numberOfLogicalViews para detalhes sobre esse limite. Outra causa é ter bibliotecas JSF duplicadas em um caminho de classe de tempo de execução conflitando entre si. O procedimento correto para instalar o JSF é descrito em nossa página wiki do JSF .

Manipulando ViewExpiredException

Quando você quiser lidar com um inevitável ViewExpiredException após uma ação POST em uma página arbitrária que já foi aberta em alguma aba/janela do navegador enquanto você estiver desconectado em outra aba/janela, então você gostaria de especifique um error-page para aquele em web.xml que vai para a 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 você pretenda realmente redirecionar para a página inicial ou 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 0 em content representa a quantidade de segundos antes do redirecionamento, 0 significa "redirecionar imediatamente", você pode usar por exemplo 3 para deixar o navegador esperar 3 segundos com o redirecionamento)

Observe que o tratamento de exceções durante solicitações ajax requer um ExceptionHandler especial. Veja também Tempo limite da sessão e manipulação de ViewExpiredException no pedido ajax JSF/PrimeFaces . Você pode encontrar um exemplo ao vivo em OmniFaces FullAjaxExceptionHandler page showcase (isso também abrange pedidos não-ajax).

Observe também que sua página de erro "geral" deve ser mapeada em <error-code> de 500 em vez de <exception-type> de, por exemplo, Java.lang.Exception ou Java.lang.Throwable, caso contrário, todas as exceções envolvidas em ServletException, como ViewExpiredException, ainda terminariam na página de erro geral. Veja 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 as visualizações JSF no modo sem estado. Dessa forma, nada do estado do JSF será salvo e as visualizações nunca irão expirar, apenas serão reconstruídas do zero em todas as solicitações. Você pode ativar as visualizações sem estado configurando o atributo transient de <f:view> para true:

<f:view transient="true">

</f:view>

Desta forma, o campo oculto javax.faces.ViewState irá obter um valor fixo de "stateless" em Mojarra (não verificou MyFaces neste momento). Note que este recurso foi introduzido em Mojarra 2.1.19 e 2.2.0 e não está disponível em versões mais antigas.

A consequência é que você não pode mais usar os beans com escopo de visão. Agora eles se comportarão como um feijão com escopo de pedidos. Uma das desvantagens é que você precisa rastrear o estado manipulando entradas ocultas e/ou parâmetros de solicitação perdidos. Principalmente aqueles formulários com campos de entrada com atributos rendered, readonly ou disabled que são controlados por eventos ajax serão afetados.

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

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

e no template client:

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

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

Veja também


Não relacionado ao problema concreto, usando HTTP POST para navegação de página para página pura não é muito usuário/SEO amigável. No JSF 2.0, você deve preferir <h:link> ou <h:button> sobre <h:commandXxx> para a navegação simples de página para página do Vanilla.

Então, ao invés de, e.

<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

332
BalusC

Você 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.

55
Mike GH

Primeiro o que você tem que fazer, antes de mudar web.xml é ter certeza que seu ManagedBean implements Serializable:

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

Especialmente se você usar MyFaces

5
ZuzEL

Evite formulários multipartes no Richfaces:

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

Se você estiver usando Richfaces, descobri que as solicitações de ajax dentro de formulários multipartes retornam uma nova ID de exibição em cada solicitação.

como depurar:

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

3
tak3shi

Você pode usar seu próprio costume AjaxExceptionHandler ou primefaces-extensions

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 />
...
0
myset

Quando nossa página estiver inativa por um período de xx, a visualização expirará e lançará javax.faces.application.ViewExpiredException para evitar que isso aconteça. Uma solução é criar CustomViewHandler que estenda ViewHandler e substituir o método restoreView. Todos os outros métodos estão sendo delegados para o 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

0
user4924157

Eu estava recebendo este erro: javax.faces.application.ViewExpiredException.When eu usando diferentes solicitações, encontrei aqueles com mesmo JsessionId, mesmo depois de reiniciar o servidor. Então, isso é devido ao cache do navegador. Basta fechar o navegador e tente, vai funcionar.

0
rahul