javax.faces.application.ViewExpiredException: представление не может быть восстановлено - PullRequest
163 голосов
/ 04 сентября 2010

Я написал простое приложение с безопасностью, управляемой контейнером.Проблема в том, что когда я захожу и открываю другую страницу, на которой я выхожу, я возвращаюсь на первую страницу и нажимаю на любую ссылку и т. Д. Или обновляю страницу, на которой появляется это исключение.Я думаю, что это нормально (или, возможно, нет :)), потому что я вышел из системы и сеанс уничтожен.Что я должен сделать, чтобы перенаправить пользователя, например, на index.xhtml или login.xhtml и спасти его от просмотра этой страницы / сообщения об ошибке?

Другими словами, как я могу автоматически перенаправить другие страницы на страницу index / login послеВыйти?

Вот оно:

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)

Ответы [ 10 ]

332 голосов
/ 04 сентября 2010

Введение

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" />

Смотри также

55 голосов
/ 14 мая 2012

Вы пытались добавить строки ниже к вашему web.xml?

<context-param>
   <param-name>com.sun.faces.enableRestoreView11Compatibility</param-name>
   <param-value>true</param-value>
</context-param>

Я обнаружил, что это очень эффективно, когда я столкнулся с этой проблемой.

5 голосов
/ 28 июля 2013

Прежде чем изменить web.xml , сначала убедитесь, что ваш ManagedBean implements Serializable:

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

Особенно, если вы используете MyFaces * 1008.*

3 голосов
/ 24 марта 2017

Избегайте составных форм в Richfaces:

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

Если вы используете Richfaces, я обнаружил, что запросы ajax внутри многокомпонентных форм возвращают новый View ID для каждого запроса.

Как отлаживать:

При каждом ajax-запросе возвращается идентификатор представления, это нормально, если идентификатор представления всегда одинаков. Если вы получаете новый View ID при каждом запросе, значит, существует проблема, и ее необходимо исправить.

0 голосов
/ 21 мая 2015

Когда наша страница простаивает в течение 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;
    }

}   

Затем вам нужно добавить его в файл Face-config.xml

<application>
    <view-handler>com.demo.CustomViewHandler</view-handler>
</application>

Спасибо за оригинальный ответ по ссылке ниже: http://www.gregbugaj.com/?p=164

0 голосов
/ 05 октября 2014

Я получаю эту ошибку: javax.faces.application.ViewExpiredException. Когда я использовал разные запросы, я обнаружил, что те же JsessionId, даже после перезапуска сервера. Так что это связано с кешем браузера. Просто закройте браузер и попробуйте, он будет работать.

0 голосов
/ 25 июня 2014

Я сам столкнулся с этой проблемой и понял, что это связано с побочным эффектом созданного мной фильтра, который фильтровал все запросы приложения. Как только я изменил фильтр, чтобы выбрать только определенные запросы, эта проблема не возникла. Может быть, стоит проверить такие фильтры в вашем приложении и посмотреть, как они себя ведут.

0 голосов
/ 13 июня 2014

Пожалуйста, добавьте эту строку в ваш web.xml. Это работает для меня

<context-param>
        <param-name>org.ajax4jsf.handleViewExpiredOnClient</param-name> 
        <param-value>true</param-value>     
    </context-param>
0 голосов
/ 11 января 2014

Вы можете использовать свой собственный AjaxExceptionHandler или primefaces-extensions

Обновите ваши лица-config.xml

...
<factory>
  <exception-handler-factory>org.primefaces.extensions.component.ajaxerrorhandler.AjaxExceptionHandlerFactory</exception-handler-factory>
</factory>
...

Добавьте следующий код на странице jsf

...
<pe:ajaxErrorHandler />
...
0 голосов
/ 10 ноября 2012

Я добавляю следующую конфигурацию в 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>
...