javax.faces.application.ViewExpiredException: представление не может быть восстановлено

175

Я написал простое приложение с безопасностью, управляемой контейнером. Проблема в том, что когда я захожу и открываю другую страницу, на которой я выхожу, я возвращаюсь на первую страницу и нажимаю на любую ссылку и т. Д. Или обновляю страницу, на которой появляется это исключение. Я думаю, что это нормально (или, возможно, нет :)), потому что я вышел из системы, и сессия уничтожена. Что я должен сделать, чтобы перенаправить пользователя, например, на 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)
l245c4l
источник

Ответы:

353

Введение

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>

( 0in 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" />

Смотрите также

BalusC
источник
Как я могу сделать это с неявной навигацией в Java EE 6? Я не использую лица-конфигурации.
l245c4l
1
О, вы используете JSF 2.0? Вы должны были упомянуть об этом в своем вопросе! Добавьте ?faces-redirect=trueк outcome. Я обновил ответ соответственно.
BalusC
Да, я только начал с java ee :), и я использую Face-Redirect = True во всех моих навигации. Я использую h: commandLink только тогда, когда у меня есть действия, связанные с ним. Например, ссылка «Выход из системы» ... У меня есть действие String logout (), где я делаю недействительным сеанс и перенаправляю его на страницу входа, но она не работает на странице, на которой я вошел в систему и в настоящий момент вышел из системы, и выдает это исключение :(
l245c4l
1
Еще раз спасибо и извините за это :), но по крайней мере я получил быстрый и профессиональный ответ в
кратчайшие
1
@LS: Фильтр все же обязателен для случая, когда каждый нажимает кнопку «Назад» после истекшего POST и пытается вызвать другой POST-запрос к нему. В противном случае это могло бы привести к этому исключению.
BalusC
56

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

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

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

Майк Г.Х.
источник
1
это сработало и для меня. Спасибо за ответ. Какова цель этого?
MartK
2
Точно не помню, но я нашел это решение на сайте ICEFaces.
Майк Г.Х.
Я могу немного опоздать на вечеринку, но это сработало и для меня. Спасибо!
stellarossa
4
Это определяется только для JSF 1.2 или JSF 2?
SRy
17
Это прекратит генерировать исключение по истечении срока действия представления и просто продолжит запрос, но JSF по-прежнему не сможет ни восстановить состояние представления, ни найти какие-либо связанные bean-объекты вида. Эта транзакция будет вести себя как JSF без сохранения состояния, и вам придется самостоятельно восстанавливать состояние представления на основе параметров запроса POST, чтобы избежать "wtf?" Опыт конечного пользователя при обработке формы отправки неожиданно отреагировал. Если вы хотите применить это только к определенным страницам JSF, то лучше использовать OmniFaces <o:enableRestorableView>вместо параметра контекста приложения.
BalusC
5

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

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

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

ZuzEL
источник
3

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

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

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

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

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

tak3shi
источник
Будьте осторожны при игре с опросами, это может помешать срокам ваших пользователей истечь ..
Джонатан Симас
0

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

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

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

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

...
<pe:ajaxErrorHandler />
...
ои
источник
0

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

Рахул
источник
0

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

user4924157
источник
Этот подход не восстанавливает bean-объекты вида.
BalusC
-2

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

<context-param>
        <param-name>org.ajax4jsf.handleViewExpiredOnClient</param-name> 
        <param-value>true</param-value>     
    </context-param>
harshal
источник
4
Ваш ответ будет более полезным, если вы включите объяснение и контекст о том, что делает код.
Палпатим
1
Даже если это правильный ответ, StackOverflow не одобряет подобные ответы без объяснения причин. Для сообщества было бы полезно добавить больше информации о том, почему это работает.
RacerNerd
-2

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

javshak
источник
-3

Я добавил следующую конфигурацию в 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>
Мухаммед Али
источник
6
Страшный совет. Делайте это только тогда, когда у вас достаточно памяти и у ваших конечных пользователей действительно открыты до 500 вкладок браузера, и нажмите кнопку назад браузера до 500 раз, чтобы перейти к предыдущим синхронным обратным передачам.
BalusC