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)

Відповіді:


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 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 (звичайні посилання / кнопки) для навігації зі сторінки на сторінку та натискаєте таку командну посилання / кнопку на кешованій сторінці, то це в свою чергу провал з aViewExpiredException .

Щоб запустити переадресацію після виходу з програми в 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 vs 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.Сторінка помилок у веб.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"в Mojarra (не перевіряли MyFaces на даний момент). Зауважте, що ця функція була введена в Mojarra 2.1.19 та 2.2.0 та недоступна у старих версіях.

Наслідком цього є те, що ви вже не можете використовувати боби з переглядом. Тепер вони будуть поводитись так, як квасоля. Один з недоліків полягає в тому, що вам потрібно самостійно відстежувати стан за допомогою прихованих входів та / або вільних параметрів запиту. В основному ці форми з полями введення з 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" />

Дивитися також


Як я можу це зробити з неявною навігацією в java ee 6? Я не використовую face-config.
l245c4l

1
О, ви використовуєте JSF 2.0? Ви повинні це згадати у своєму питанні! Додати ?faces-redirect=trueв outcome. Відповідно я оновив відповідь.
BalusC

Так, я тільки почав з java ee :), і я використовую face-redirect = true у всіх своїх навігаціях. Я використовую h: commandLink лише тоді, коли в мене пов’язані дії. Наприклад, посилання Вихід ... У мене є дія String logout (), де я визнаю недійсним сеанс і перенаправляю на вхід, але він не працює на сторінці, де я був увійшов у систему, і в даний момент вийшов із системи, і викидає це виняток :(
l245c4l

1
Ще раз дякую і вибачте за це :), але принаймні я швидко і професійно відповів за короткий час: p
l245c4l

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.
Майк GH

я можу трохи запізнитися на вечірку, але це працювало і для мене. Дякую!
stellarossa

4
Чи визначено це лише для JSF 1.2 або JSF 2?
SRy

17
Це вимкне викид винятку, коли термін дії перегляду закінчиться, і просто продовжуйте запит, але JSF все одно не зможе відновити стан перегляду, а також не знайде жодної пов’язаної із переглянутою квасолею. Ця транзакція буде вести себе як JSF без стану, і вам потрібно буде самостійно відновити стан перегляду на основі параметрів запиту POST, щоб уникнути "wtf?" досвід ендюсера при обробці форми подання відповів несподівано. Якщо ви хочете застосувати це лише на певних сторінках JSF, скоріше використовуйте OmniFaces <o:enableRestorableView>замість контекстного параметра для широкого застосування.
BalusC

5

Перш за все, що вам потрібно зробити, перш ніж змінювати web.xml, це переконатися, що ваш ManagedBean implements Serializable:

@ManagedBean
@ViewScoped
public class Login implements Serializable {
}

Особливо, якщо ви використовуєте MyFaces


3

Уникайте багаточастинкових форм у Richface:

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

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

Як налагодити:

Для кожного запиту ajax повертається ідентифікатор перегляду, це добре, якщо ідентифікатор перегляду завжди однаковий. Якщо ви отримуєте новий ідентифікатор перегляду на кожен запит, виникла проблема, яку потрібно виправити.


Будьте обережні, граючи з опитуваннями, це може запобігти закінченню сеансів ваших користувачів ..
Джонатан Сімас

0

Ви можете використовувати свої власні користувальницькі AjaxExceptionHandler або розширення з розширеннями

Оновіть обличчя-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 і замінює метод RestoView, всі інші методи делегуються Батьківський

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


Цей підхід не відновлює квасоля з переглядом.
BalusC

-2

Будь ласка, додайте цей рядок у свій web.xml Він працює для мене

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

4
Ваша відповідь буде кориснішою, якщо ви додасте пояснення та контекст щодо того, що робить код.
Палпатім

1
Навіть якщо це правильна відповідь, StackOverflow відштовхує подібні відповіді без пояснень. Було б корисно громаді додати більше інформації про те, чому це працює.
RacerNerd

-2

Я сам зіткнувся з цією проблемою і зрозумів, що це було через побічний ефект фільтра, який я створив, який фільтрував усі запити на пристрої. Як тільки я змінив фільтр, щоб вибрати лише певні запити, цієї проблеми не виникло. Можливо, добре перевірити наявність таких фільтрів у вашій програмі та побачити, як вони поводяться.


-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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.