Ответ 1
В принципе, вы хотите, чтобы состояние просмотра JSF и все видимые области видимости beans были уничтожены во время выгрузки окна. Решение было реализовано в OmniFaces @ViewScoped
аннотация, которая описана в документации, как показано ниже:
Могут быть случаи, когда желательно немедленно уничтожить область с видимым видом bean, а также при вызове браузера
unload
. То есть когда пользователь переходит от GET или закрывает вкладку/окно браузера. Ни одна из аннотаций аннотации представления JSF 2.2 не поддерживает это. Начиная с OmniFaces 2.2, аннотация этого вида представления CDI гарантирует, что аннотированный метод@PreDestroy
также вызывается при разгрузке браузера. Этот трюк выполняется синхронным запросом XHR через автоматически включаемый помощник scriptomnifaces:unload.js
. Однако существует небольшая оговорка: на медленной сети и/или на плохом серверном оборудовании может быть заметное отставание между действием enduser разгрузки страницы и желаемым результатом. Если это нежелательно, тогда лучше придерживаться аннотаций к собственному виду JSF 2.2 и принять отложенную версию.Начиная с OmniFaces 2.3, выгрузка была дополнительно улучшена, чтобы также физически удалить связанное состояние представления JSF из внутренней карты LRU реализации JSF в случае сохранения состояния на стороне сервера, тем самым дополнительно уменьшая риск при
ViewExpiredException
на других представлениях, которые были созданы/открыты ранее. В качестве побочного эффекта этого изменения, аннотированный метод@PreDestroy
любого стандартного представления JSF с привязкой beans, на который ссылается в том же представлении, что и просмотр содержимого CDI в формате OmniFaces, ограниченный bean, также будет активирован при разгрузке браузера.
Здесь вы можете найти соответствующий исходный код:
- Инициализатор выгрузки script:
ViewScopeManager#registerUnloadScript()
- Выгрузить script себя:
unload.unminified.js
- Обработчик вида выгрузки:
OmniViewHandler#unloadView()
- Разрушитель состояния представления JSF:
Hacks#removeViewState()
Выгрузка script будет запускаться во время окна beforeunload
событие, , если оно не вызвано каким-либо JSF (ajax). Что касается коммандлинка и/или ajax, это специфично для реализации. В настоящее время распознаются Mojarra, MyFaces и PrimeFaces.
Разгрузка script будет trigger navigator.sendBeacon
в современных браузерах и вернуться к синхронному XHR (асинхронный сбой, поскольку страница может быть выгружена раньше, чем запрос на самом деле попадает на сервер).
var url = form.action;
var query = "omnifaces.event=unload&id=" + id + "&" + VIEW_STATE_PARAM + "=" + encodeURIComponent(form[VIEW_STATE_PARAM].value);
var contentType = "application/x-www-form-urlencoded";
if (navigator.sendBeacon) {
// Synchronous XHR is deprecated during unload event, modern browsers offer Beacon API for this which will basically fire-and-forget the request.
navigator.sendBeacon(url, new Blob([query], {type: contentType}));
}
else {
var xhr = new XMLHttpRequest();
xhr.open("POST", url, false);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.setRequestHeader("Content-Type", contentType);
xhr.send(query);
}
Обработчик вида выгрузки будет явно уничтожить все @ViewScoped
beans, включая стандартные JSF (обратите внимание, что разгрузка script инициализируется только тогда, когда представление ссылается хотя бы на один OmniFaces @ViewScoped
bean).
context.getApplication().publishEvent(context, PreDestroyViewMapEvent.class, UIViewRoot.class, createdView);
Однако это не разрушает физическое состояние представления JSF в сеансе HTTP, и, таким образом, use case не будет выполняться:
- Задайте количество физических представлений до 3 (в Mojarra используйте параметр контекста
com.sun.faces.numberOfLogicalViews
и в MyFaces используйте параметрorg.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION
context). - Создайте страницу, которая ссылается на стандартный JSF
@ViewScoped
bean. - Откройте эту страницу на вкладке и сохраните ее все время.
- Откройте ту же страницу на другой вкладке и сразу закройте эту вкладку.
- Откройте ту же страницу на другой вкладке и сразу закройте эту вкладку.
- Откройте ту же страницу на другой вкладке и сразу закройте эту вкладку.
- Отправьте форму на первой вкладке.
Это приведет к ошибке с ViewExpiredException
, потому что состояния представления JSF ранее закрытых вкладок физически не уничтожаются во время PreDestroyViewMapEvent
. Они все еще остаются на сессии. OmniFaces @ViewScoped
фактически уничтожит их. Уничтожение состояния представления JSF, однако, специфично для реализации. Это объясняет, по крайней мере, довольно хакерский код в классе Hacks
, который должен достичь этого.
Тест интеграции для этого конкретного случая можно найти в ViewScopedIT#destroyViewState()
на ViewScopedIT.xhtml
, который в настоящее время работает с WildFly 10.0.0, TomEE 7.0.1 и Payara 4.1.1.163.
Вкратце: просто замените javax.faces.view.ViewScoped
на org.omnifaces.cdi.ViewScoped
. Остальное прозрачно.
import javax.inject.Named;
import org.omnifaces.cdi.ViewScoped;
@Named
@ViewScoped
public class Bean implements Serializable {}
Я по крайней мере сделал попытку предложить общедоступный API-метод для физического разрушения состояния представления JSF. Возможно, это произойдет в JSF 2.3, а затем я смогу устранить шаблон в классе OmniFaces Hacks
. Как только предмет полируется в OmniFaces, он, возможно, в конечном итоге попадет в JSF, но не раньше 2.4.