Введение
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()
на сервере, либо из-за специфической для сервера ошибки сСеансовые куки, как известно в WildFly ), затем сериализованное состояние просмотра больше не доступно в сеансе, и конечный пользователь получит это исключение.Чтобы понять, как работает сессия, см. Также Как работают сервлеты?Создание экземпляров, сеансы, общие переменные и многопоточность .
Существует также ограничение на количество просмотров, которые JSF будет хранить в сеансе.Когда предел достигнут, то срок действия наименее использованного представления истекает.См. Также com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews .
Если для метода сохранения состояния установлено значение 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 будет хранить в сеансе.Таким образом, это произойдет только тогда, когда вы вручную установите слишком низкий предел или когда вы постоянно создаете новые представления в «фоне» (например, из-за плохо реализованного опроса ajax на той же странице или из-за плохо реализованного 404страница ошибки на разбитых изображениях той же страницы).См. Также com.sun.faces.numberOfViewsInSession против com.sun.faces.numberOfLogicalViews для получения подробной информации об этом пределе.Другая причина - наличие дублирующих библиотек 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 в запросе JSF / PrimeFaces ajax .Вы можете найти живой пример на 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>
, чтобы сделать его условным. Обратите внимание, что это применимо к представлению whole , а не только к вложенному содержимому, такому как <h:form>
в приведенном выше примере.
Смотри также
Не имеет отношения к конкретной проблеме, использование 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" />
Смотри также