Введение
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 будет хранить в сеансе. Когда предел достигнут, то срок действия наименее использованного представления истекает. Смотрите также com.sun.faces.numberOfViewsInSession против 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
in content
представляет количество секунд до перенаправления, 0
что означает «немедленное перенаправление», вы можете использовать, например, 3
чтобы браузер подождал 3 секунды с перенаправлением)
Обратите внимание, что обработка исключений во время запросов ajax требует специального ExceptionHandler
. См. Также Тайм-аут сеанса и Обработка ViewExpiredException по запросу AJAX JSF / PrimeFaces . Вы можете найти живой пример на странице FullAjaxExceptionHandler
демонстрации OmniFaces (это также относится и к запросам не-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
атрибутов , которые контролируются АЯКС события будут затронуты.
Обратите внимание, что <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>
в приведенном выше примере.
Смотрите также
Вне зависимости от конкретной проблемы, использование 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" />
Смотрите также