Introdução
O ViewExpiredException
será lançada sempre que o javax.faces.STATE_SAVING_METHOD
está 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.ViewState
de 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.ViewState
campo 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 ViewExpiredException
quando 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 ViewExpiredException
quando, 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.ViewState
campo 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=true
ao outcome
valor.
<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 Filter
que seja mapeada no nome do servlet 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 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 ViewExpiredException
atualizaçã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 ViewExpiredException
apó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-page
para o web.xml
que 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 0
in content
representa a quantidade de segundos antes do redirecionamento, 0
portanto significa "redirecionar imediatamente", você pode usar, por exemplo, 3
para 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 500
vez de um <exception-type>
de, por exemplo java.lang.Exception
, ou java.lang.Throwable
, caso contrário, todas as exceções envolto em ServletException
como ViewExpiredException
ainda 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 transient
atributo de <f:view>
para true
:
<f:view transient="true">
</f:view>
Dessa forma, o javax.faces.ViewState
campo 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
, readonly
ou disabled
atributos 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