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)

Ответы

Ответ 1

Введение

ViewExpiredException будет вызываться всякий раз, когда для параметра javax.faces.STATE_SAVING_METHOD установлено значение server (по умолчанию), и enduser отправляет запрос HTTP POST на представление через <h:form> с помощью <h:commandLink>, <h:commandButton> или <f:ajax>, а связанное состояние просмотра больше не доступно в сеансе.

Состояние представления идентифицируется как значение скрытого поля ввода javax.faces.ViewState для <h:form>. Если метод сохранения состояния установлен в server, он содержит только идентификатор состояния представления, который ссылается на сериализованное состояние представления в сеансе. Таким образом, по истечении срока действия сеанса по какой-либо причине (либо время ожидания на сервере или на стороне клиента, либо cookie сеанса больше не поддерживается по какой-либо причине в браузере или вызовом HttpSession#invalidate() на сервере или из-за ошибки сервера с как известно в WildFly), тогда сериализованное состояние представления больше не доступно в сеансе, и enduser получит это исключение. Чтобы понять работу сессии, см. Также Как работают сервлеты? Создание, сеансы, общие переменные и многопоточность.

Также существует ограничение на количество просмотров, которое JSF будет хранить в сеансе. Когда предел будет удачен, будет уменьшено время последнего использования. См. Также com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews.

Когда метод сохранения состояния установлен в client, скрытое поле ввода javax.faces.ViewState содержит вместо этого все сериализованное состояние представления, поэтому конечный пользователь не получит ViewExpiredException, когда истекает срок действия сеанса. Однако это может происходить и в среде кластера ( "ОШИБКА: MAC не проверяется" является симптоматичной) и/или когда существует определенный тайм-аут, зависящий от реализации, в состоянии клиентской стороны и/или когда сервер повторно генерирует ключ AES во время перезапуска, см. также Получение ViewExpiredException в кластерной среде, в то время как для метода сохранения состояния установлено значение клиент и сеанс пользователя. Как его решить.

Независимо от решения, убедитесь, что вы используете не enableRestoreView11Compatibility. он вовсе не восстанавливает исходное состояние представления. Он в основном воссоздает представление и все связанные с ним видимые области beans с нуля и тем самым теряют все исходные данные (состояние). Поскольку приложение будет вести себя запутанным образом ( "Эй, где мои входные значения...?" ), Это очень плохо для пользователей. Вместо этого лучше использовать представления без учета состояния или <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 в сеансе. Таким образом, это произойдет только в том случае, если вы вручную установили слишком низкий лимит или что вы постоянно создаете новые представления в фоновом режиме (например, с помощью плохо проведенного опроса). См. Также com.sun.faces.numberOfViewsInSession vs com.sun.faces.numberOfLogicalViews. Другая причина заключается в том, что дублирующие JSF-библиотеки в пуле классов выполнения конфликтуют друг с другом. Правильная процедура установки JSF описана в нашей странице вики JSF.

Обработка ViewExpiredException

Если вы хотите обработать неотвратимый ViewExpiredException после действия POST на произвольной странице, которая была уже открыта в какой-либо вкладке/окне браузера, когда вы вышли из системы в другой вкладке/окне, то вы хотите указать a 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 витрины (это также охватывает запросы без аякса).

Также обратите внимание, что ваша "общая" страница ошибки должна отображаться на <error-code> из 500 вместо <exception-type>, например. java.lang.Exception или java.lang.Throwable, в противном случае все исключения, заключенные в ServletException, такие как ViewExpiredException, по-прежнему будут отображаться на общей странице ошибок. См. Также ViewExpiredException, показанное на странице java.lang.Throwable error-страницы в 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 и недоступна в более старых версиях.

Следствием этого является то, что вы больше не можете использовать область видимости beans. Теперь они будут вести себя как область запроса beans. Один из недостатков заключается в том, что вы должны отслеживать состояние самостоятельно, играя со скрытыми входами и/или свободными параметрами запроса. В основном будут затронуты те формы с полями ввода с атрибутами 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> в приведенном выше примере.

См. также


Несвязанный к конкретной проблеме, использование 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" />

См. также

Ответ 2

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

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

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

Ответ 3

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

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

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

Ответ 4

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

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

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

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

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

Ответ 5

Каждый раз, когда представление отображается в JSF, который использует состояние, это состояние где-то сохраняется. В случае сохранения состояния на клиенте все объекты и их примитивы, составляющие это состояние, сериализуются и в виде (зашифрованной) base64 кодированной строки, отправленной клиенту. Если состояние сохранено на сервере, клиенту отправляется только идентификатор состояния представления.

Этот идентификатор состояния представления указывает на захват состояния, сохраненного в памяти сервера. Сделать этот "захват" можно, просто сохраняя указатели (мелкую копию), или делая то же самое, что и для состояния на клиенте, и сериализуя все (делая глубокую копию).

MyFaces сериализует состояние по умолчанию и может быть отключено добавлением следующей конфигурации в web.xml.

<context-param>
    <param-name>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</param-name>
    <param-value>false</param-value>
</context-param>

Mojarra по умолчанию не сериализуется.

Ответ 6

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

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

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

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

...
<pe:ajaxErrorHandler />
...

Ответ 7

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

Ответ 8

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

}   

Затем вам нужно добавить его в ваши faces-config.xml

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

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

Ответ 10

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

<context-param>
        <param-name>org.ajax4jsf.handleViewExpiredOnClient</param-name> 
        <param-value>true</param-value>     
    </context-param>

Ответ 11

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

Ответ 12

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