javax.faces.application.ViewExpiredException: представление не может быть восстановлено
Я написал простое приложение с безопасностью, управляемой контейнером. Проблема в том, что когда я захожу и открываю другую страницу, на которой я выхожу, я возвращаюсь на первую страницу и нажимаю на любую ссылку и т. Д. Или обновляю страницу, на которой появляется это исключение. Я думаю, что это нормально (или, возможно, нет:)), потому что я вышел из системы и сеанс уничтожен. Что я должен сделать, чтобы перенаправить пользователя, например, на index.xhtml или login.xhtml и спасти его от просмотра этой страницы / сообщения об ошибке?
Другими словами, как я могу автоматически перенаправить другие страницы на страницу индекса / входа после выхода из системы?
Вот:
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)
11 ответов
Вступление
ViewExpiredException
будет брошен всякий раз, когда javax.faces.STATE_SAVING_METHOD
установлен в server
(по умолчанию) и конечный пользователь отправляет HTTP-запрос POST для просмотра через <h:form>
с <h:commandLink>
, <h:commandButton>
или же <f:ajax>
в то время как связанное состояние просмотра больше не доступно в сеансе.
Состояние просмотра определяется как значение скрытого поля ввода javax.faces.ViewState
из <h:form>
, С методом сохранения состояния, установленным на server
он содержит только идентификатор состояния представления, который ссылается на сериализованное состояние представления в сеансе. Таким образом, когда сеанс истек по какой-либо причине (либо истек тайм-аут на стороне сервера или клиента, либо cookie-файл сеанса больше не поддерживается по какой-либо причине в браузере, либо путем вызова HttpSession#invalidate()
на сервере или из-за специфической ошибки сервера с файлами cookie сеанса, известной в WildFly), состояние сериализованного представления больше не будет доступно в сеансе, и конечный пользователь получит это исключение. Чтобы понять работу сеанса, см. Также Как работают сервлеты? Создание экземпляров, сессий, общих переменных и многопоточности.
Существует также ограничение на количество просмотров, которые JSF будет хранить в сеансе. Когда предел достигнут, то срок действия наименее использованного представления истекает. Смотрите также /questions/12284507/comsunfacesnumberofviewsinsession-protiv-comsunfacesnumberoflogicalviews.
С методом сохранения состояния, установленным на client
, javax.faces.ViewState
скрытое поле ввода содержит вместо этого все состояние сериализованного представления, поэтому конечный пользователь не получит ViewExpiredException
когда сессия истекает. Однако это все еще может происходить в кластерной среде ("ОШИБКА: MAC не проверил" является симптоматичным) и / или когда настроен тайм-аут для реализации на настроенном состоянии на стороне клиента и / или когда сервер повторно генерирует ключ AES во время перезапуска см. также Получение ViewExpiredException в кластерной среде, в то время как метод сохранения состояния установлен на клиенте, а пользовательский сеанс действителен, как его решить.
Независимо от решения, убедитесь, что вы не используете enableRestoreView11Compatibility
, это вовсе не восстанавливает исходное состояние просмотра. По сути, он воссоздает представление и все связанные с ним объекты с нуля и тем самым теряет все исходные данные (состояние). Поскольку приложение будет вести себя непонятно ("Привет, где мои входные значения...??"), это очень плохо для пользовательского опыта. Лучше использовать представления без гражданства или <o:enableRestorableView>
вместо этого, чтобы вы могли управлять им только в определенном виде, а не во всех.
Что касается того, почему JSF нужно сохранять состояние просмотра, перейдите к следующему ответу: почему JSF сохраняет состояние компонентов пользовательского интерфейса на сервере?
Избегание исключений ViewExpiredException при навигации по страницам
Чтобы избежать ViewExpiredException
например, при переходе назад после выхода из системы, когда сохранение состояния установлено на server
только перенаправления запроса POST после выхода из системы недостаточно. Вы также должны указать браузеру не кэшировать динамические страницы JSF, иначе браузер может показывать их из кэша вместо того, чтобы запрашивать новую с сервера, когда вы отправляете на нее запрос GET (например, с помощью кнопки "Назад").
javax.faces.ViewState
скрытое поле кэшированной страницы может содержать значение идентификатора состояния просмотра, которое больше не действует в текущем сеансе. Если вы (ab) используете POST (командные ссылки / кнопки) вместо GET (обычные ссылки / кнопки) для перехода между страницами и нажимаете такую командную ссылку / кнопку на кэшированной странице, то это, в свою очередь, будет потерпеть неудачу с ViewExpiredException
,
Чтобы запустить перенаправление после выхода из системы в JSF 2.0, либо добавьте <redirect />
к <navigation-case>
в вопросе (если есть), или добавить ?faces-redirect=true
к outcome
значение.
<h:commandButton value="Logout" action="logout?faces-redirect=true" />
или же
public String logout() {
// ...
return "index?faces-redirect=true";
}
Чтобы указать браузеру не кэшировать динамические страницы JSF, создайте Filter
который отображается на имя сервлета FacesServlet
и добавляет необходимые заголовки ответа, чтобы отключить кэш браузера. Например
@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);
}
// ...
}
Избежание исключений ViewExpiredException при обновлении страницы
Чтобы избежать ViewExpiredException
при обновлении текущей страницы, когда состояние сохранения установлено на server
вам нужно не только убедиться, что вы выполняете постраничную навигацию исключительно с помощью GET (обычные ссылки / кнопки), но вы также должны убедиться, что вы используете исключительно ajax для отправки форм. В любом случае, если вы отправляете форму синхронно (не в ajax), вам лучше либо оставить представление без состояния (см. Следующий раздел), либо отправить перенаправление после POST (см. Предыдущий раздел).
Иметь ViewExpiredException
при обновлении страницы в конфигурации по умолчанию очень редкий случай. Это может произойти только тогда, когда будет достигнуто ограничение на количество просмотров, которые JSF будет хранить в сеансе. Таким образом, это произойдет, только если вы вручную установите слишком низкий предел или когда вы постоянно создаете новые представления в фоновом режиме (например, из-за плохо реализованного опроса). Смотрите также /questions/12284507/comsunfacesnumberofviewsinsession-protiv-comsunfacesnumberoflogicalviews. Другая причина - наличие дублирующих библиотек JSF в пути к классам во время выполнения, конфликтующих друг с другом. Правильная процедура установки JSF описана на нашей вики-странице JSF.
Обработка ViewExpiredException
Когда вы хотите справиться с неизбежным ViewExpiredException
после действия POST на произвольной странице, которая уже была открыта в какой-либо вкладке / окне браузера, когда вы вышли из системы в другой вкладке / окне, вам нужно указать error-page
для этого в web.xml
которая переходит на страницу "Время сеанса истекло". Например
<error-page>
<exception-type>javax.faces.application.ViewExpiredException</exception-type>
<location>/WEB-INF/errorpages/expired.xhtml</location>
</error-page>
При необходимости используйте заголовок метаобновления на странице ошибок, если вы действительно хотите перенаправить дальше на домашнюю страницу или страницу входа.
<!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
в content
представляет количество секунд до перенаправления, 0
таким образом, означает "перенаправить немедленно", вы можете использовать, например, 3
дать браузеру подождать 3 секунды с перенаправлением)
Обратите внимание, что обработка исключений во время запросов ajax требует специального ExceptionHandler
, См. Также Тайм-аут сеанса и Обработка ViewExpiredException по запросу AJAX JSF / PrimeFaces. Вы можете найти живой пример на OmniFaces FullAjaxExceptionHandler
страница демонстрации (это также покрывает не-AJAX-запросы).
Также обратите внимание, что ваша "общая" страница ошибки должна отображаться на <error-code>
из 500
вместо <exception-type>
например java.lang.Exception
или же java.lang.Throwable
иначе все исключения обернуты в ServletException
такие как ViewExpiredException
все равно оказался бы на странице общих ошибок. См. Также ViewExpiredException, показанное в java.lang.Throwable error-page в web.xml.
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/errorpages/general.xhtml</location>
</error-page>
Представления без гражданства
Совершенно другой альтернативой является запуск представлений JSF в режиме без сохранения состояния. Таким образом, ничто из состояния JSF не будет сохранено, и представления никогда не будут устаревать, а будут перестраиваться с нуля при каждом запросе. Вы можете включить режим просмотра без сохранения состояния, установив transient
атрибут <f:view>
в true
:
<f:view transient="true">
</f:view>
Таким образом, javax.faces.ViewState
скрытое поле получит фиксированное значение "stateless"
в Мохарре (не проверял MyFaces на данный момент). Обратите внимание, что эта функция была введена в Mojarra 2.1.19 и 2.2.0 и недоступна в более старых версиях.
В результате вы больше не можете использовать bean-объекты вида. Теперь они будут вести себя как бобы в области запроса. Одним из недостатков является то, что вы должны отслеживать состояние самостоятельно, возиться со скрытыми входными данными и / или потерять параметры запроса. В основном те формы с полями ввода с rendered
, readonly
или же disabled
атрибуты, которые управляются событиями AJAX, будут затронуты.
Обратите внимание, что <f:view>
не обязательно должен быть уникальным во всем представлении и / или находиться только в главном шаблоне. Также вполне законно переопределить и вложить его в шаблонный клиент. Это в основном "расширяет" родителя <f:view>
затем. Например, в мастер-шаблоне:
<f:view contentType="text/html">
<ui:insert name="content" />
</f:view>
и в шаблоне клиента:
<ui:define name="content">
<f:view transient="true">
<h:form>...</h:form>
</f:view>
</f:view>
Вы можете даже обернуть <f:view>
в <c:if>
сделать это условным. Обратите внимание, что он будет применяться ко всему представлению, а не только к вложенному содержимому, такому как <h:form>
в приведенном выше примере.
Смотрите также
- ViewExpiredException показано в java.lang.Throwable страница ошибки в web.xml
- Проверьте, существует ли сессия JSF
- Тайм-аут сеанса и обработка исключений ViewExpiredException по запросу AJAX JSF / PrimeFaces
Вне зависимости от конкретной проблемы, использование HTTP POST для простой постраничной навигации не очень удобно для пользователя /SEO. В JSF 2.0 вы действительно должны предпочитать <h:link>
или же <h:button>
над <h:commandXxx>
те для простой ванильной постраничной навигации.
Так, например, вместо
<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>
лучше сделать
<h:link value="Foo" outcome="foo" />
<h:link value="Bar" outcome="bar" />
<h:link value="Baz" outcome="baz" />
Смотрите также
Вы пытались добавить строки ниже к вашему web.xml
?
<context-param>
<param-name>com.sun.faces.enableRestoreView11Compatibility</param-name>
<param-value>true</param-value>
</context-param>
Я обнаружил, что это очень эффективно, когда я столкнулся с этой проблемой.
Прежде чем изменить файл web.xml, сначала убедитесь, что ваш ManagedBean implements Serializable
:
@ManagedBean
@ViewScoped
public class Login implements Serializable {
}
Особенно если вы используете MyFaces
Я решил эту проблему в JAVA EE 8, используя тег AjaxExceptionHandler Primefaces, который доступен в Primefaces 7 или выше (я использую и тестирую в версии 11). Это так просто, и вы можете комбинировать это с пользовательским ExceptionHandlerWrapper, как предлагает BalusC. Используйте такое событие onShow, если вам нужно, чтобы перезагрузка страницы была автоматической.
<p:ajaxExceptionHandler type="javax.faces.application.ViewExpiredException"
update="viewExpiredDialog"
onexception="PF('viewExpiredDialog').show();" />
<p:dialog id="viewExpiredDialog" header="Session expired"
widgetVar="viewExpiredDialog" height="250px"
onShow="document.location.href = document.location.href;">
<h3>Reloading page</h3>
<p>Message...</p>
<!--Here, you decide that you need-->
<h:commandButton value="Reload" action="index?faces-redirect=true" />
<a href="#{request.contextPath}/login.xhtml">Reload</a>.
</p:dialog>
Добавьте эту конфигурацию в файл Faces-config.xml . См. ExceptionHandler и обработка ошибок .
<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="2.3" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd">
<application>
<el-resolver>
org.primefaces.application.exceptionhandler.PrimeExceptionHandlerELResolver
</el-resolver>
</application>
<factory>
<exception-handler-factory>
org.primefaces.application.exceptionhandler.PrimeExceptionHandlerFactory
</exception-handler-factory>
</factory>
</faces-config>
И вуаля, все работает как часы. С Уважением.
Избегайте составных форм в Richfaces:
<h:form enctype="multipart/form-data">
<a4j:poll id="poll" interval="10000"/>
</h:form>
Если вы используете Richfaces, я обнаружил, что запросы ajax внутри многокомпонентных форм возвращают новый View ID для каждого запроса.
Как отлаживать:
При каждом ajax-запросе возвращается идентификатор представления, это нормально, если идентификатор представления всегда одинаков. Если при каждом запросе вы получаете новый View ID, значит, проблема существует и должна быть исправлена.
Когда наша страница простаивает в течение x времени, срок действия представления истекает, и создается исключение javax.faces.application.ViewExpiredException, чтобы предотвратить это. Одним из решений является создание CustomViewHandler, расширяющего ViewHandler, и переопределение метода restoreView, а все остальные методы делегируются родитель
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;
}
}
Затем вам нужно добавить его в ваш лиц-config.xml
<application>
<view-handler>com.demo.CustomViewHandler</view-handler>
</application>
Спасибо за оригинальный ответ по ссылке ниже: http://www.gregbugaj.com/?p=164
Я получаю эту ошибку: javax.faces.application.ViewExpiredException. Когда я использовал разные запросы, я обнаружил, что те же JsessionId, даже после перезапуска сервера. Так что это связано с кешем браузера. Просто закройте браузер и попробуйте, он будет работать.
Вы можете использовать свой собственный AjaxExceptionHandler или primefaces-extensions
Обновите ваши лица-config.xml
...
<factory>
<exception-handler-factory>org.primefaces.extensions.component.ajaxerrorhandler.AjaxExceptionHandlerFactory</exception-handler-factory>
</factory>
...
Добавьте следующий код на своей странице JSF
...
<pe:ajaxErrorHandler />
...
Я сам столкнулся с этой проблемой и понял, что это связано с побочным эффектом созданного мной фильтра, который фильтровал все запросы приложения. Как только я изменил фильтр, чтобы выбрать только определенные запросы, эта проблема не возникла. Может быть, стоит проверить такие фильтры в вашем приложении и посмотреть, как они себя ведут.
Пожалуйста, добавьте эту строку в ваш web.xml. Это работает для меня.
<context-param>
<param-name>org.ajax4jsf.handleViewExpiredOnClient</param-name>
<param-value>true</param-value>
</context-param>
Я добавил следующую конфигурацию в web.xml, и она была решена.
<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>