JSF 2.1 Метод ViewScopedBean @PreDestroy не вызывается

У меня есть метод в представлении Scoped Bean с аннотацией @PreDestroy и еще один с аннотацией @PostConstruct.

Метод @PostConstruct корректно вызывается каждый раз, когда я перехожу к странице, использующей этот вид с видимым охватом bean.

Однако, когда я перехожу на новую страницу (которая не использует эту область видимости bean) на <h:link/>, метод @PreDestroy никогда не вызывается.

Я не говорю о изменении вручную URL-адреса или конца сеанса, просто в случае с навигацией.

Что мне не хватает?

Заранее спасибо

Ответ 1

Это по замыслу. Он будет удален только тогда, когда действие POST приведет к навигации, которая не является обратной передачей к тому же представлению (т.е. Метод действия не вернул значение null или void, а является полноценной String, даже когда она пуста).

<h:link> генерирует ссылку GET, которая не вызывает никакого действия POST. Так как невозможно надежно уведомить серверную сторону посредством HTTP-запроса (XML), когда представление выгружено, JSF не может быть уведомлено об уничтожении bean-объекта области видимости, связанного с представлением. В таком случае bean-объект области видимости будет тогда уничтожен только по истечении сеанса или когда превышено максимальное количество логических представлений в сеансе (по умолчанию 15) и связанный вид является первым по порядку.

Если вы действительно хотите уничтожить bean-объект вида с помощью действия navigaiton, тогда лучше всего сделать это POST-запрос с помощью <h:commandLink> и выполнить перенаправление, возвращая результат навигации с параметром ?faces-redirect=true, Но это, в конце концов, не оптимизировано для SEO, поскольку боты не будут индексировать ссылки POST.

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

Теоретически это возможно с onbeforeunload события HTML DOM onbeforeunload, но это нестандартное событие, и поведение браузера не определено относительно того, что происходит, когда вы отправляете ajax-запрос во время этого события. Это иногда прибывает, но иногда также нет.

Обновление: на практике это реализовано в OmniFaces @ViewScoped начиная с OmniFaces 2.2. Первоначально с помощью синхронного XHR, а с OmniFaces 2.6 - с помощью маяка. Это работает довольно хорошо в основных браузерах. Начиная с OmniFaces 2.3, он даже немедленно уничтожает соответствующее состояние просмотра на стороне сервера JSF, а с OmniFaces 2.6 он даже немедленно уничтожает физические компоненты, тем самым дополнительно сокращая ненужное использование памяти. См. Также среди прочего JSF: Mojarra против OmniFaces @ViewScoped: @PreDestroy вызван, но бин не может быть собран мусором

Ответ 2

Я подготовил небольшой проект NetBeans, демонстрирующий, когда JSF2.2 CDI-совместимый @ViewScoped beans (javax.faces.view.ViewScoped) выпущен для сбора мусора в разных случаях навигации (для Mojarra 2.2.9, Glassfish4, NetBeans8.0.2, JDK1.7), доступный для скачать здесь. Здесь код отсутствует, см. Раздел "Загрузка".

Обработанные навигационные случаи и результаты суммируются по этому изображению:

Image showing index page using @ViewSCoped bean with JSF navigation cases to a done landing page

Чтобы отслеживать @ViewScoped beans, используйте VisualVM против Glassfish (или встроенный профилировщик NetBeans в мини-проекте) и отфильтруйте представление класса гистограммы памяти сэмплера памяти типа "webel.com.jsf". На следующем рисунке показаны абсурдные 66 экземпляров webel.com.jsf.Jsf22ViewBean после многократного экспериментирования с h: link, URL-адресом браузера GET и браузером RELOAD GET, в каких экземплярах не будет собран мусор (который вы можете протестировать с помощью кнопки VisualVM Perform GC):

enter image description here

Для сравнения, переход от index.xhtml(который использует @ViewScoped bean один раз для простой чтения EL-переменной) для done.xhtml(который вообще не использует bean) с помощью h: commandButton и выражение метода действия или строка действия приводят к выпуску @ViewScoped bean для сбора мусора (всегда есть ссылка из WeldClientProxy на @ViewScoped bean, и при перемещении вперед и назад с помощью h: commandButton WeldClientProxy перемещается от одного освобождаемого bean к другому).