Неожиданное поведение с компонентами ViewScoped при использовании кластеризации Spring-Session с Hazelcast - PullRequest
2 голосов
/ 28 мая 2019

Я работаю над внедрением кластеризации в веб-приложение Spring-Boot на основе JSF, и как только мы включили репликацию сеансов с помощью Hazelcast, мы начали замечать, что некоторые из наших страниц JSF, использующих bean-компоненты ViewScoped, перестали работать правильно.Если мы отключим репликацию сеанса и Hazelcast, странное поведение больше не будет происходить.

Я впервые заметил проблему на одной из наших страниц, которая использует компонент PrimeFaces Wizard.Значения, введенные на первой странице мастера, теряются при "отправке" второй страницы.

Затем на другой странице я заметил, что командная кнопка больше не вызывает метод actionListener на управляемом бине.Я установил точку останова в методе, и точка останова никогда не срабатывает, но страница «мигает» и обновляется до исходного состояния.Я заметил, что метод PostConstruct для управляемого компонента не вызывается снова, поэтому он не генерирует новый экземпляр компонента ViewScoped.

Ни одна из этих проблем не возникает, когда я отключаю репликацию сеанса и Hazelcast.Насколько я могу судить, осматривая сеанс и его содержимое, кажется, что сеансы создаются и должным образом сохраняются, насколько я могу судить.

Приложение представляет собой веб-приложение Spring-Boot, использующеесоединения начнут вводить JSF 2.3.7 (Mojarra), PrimeFaces 6.2 и Omnifaces 1.14.1.Изначально мы разработали приложение без какой-либо репликации сеанса, и у нас не было проблем с нашими bean-компонентами ViewScoped.

Бины ViewScoped используют аннотацию org.springframework.stereotype.Component, как вы видите в примерах joinfaces.и javax.faces.view.ViewScoped в качестве аннотации области видимости.Я также пытался ввести Weld и использовать аннотацию @Named, а также использовать старые устаревшие аннотации JSF @ManagedBean и @ViewScoped, но такое же поведение сохраняется во всех случаях.

Я прошел и убедился, что все наши ManagedBeans полностью сериализуемы, а также любые атрибуты на самих bean-компонентах.

Чтобы продемонстрировать то, что я вижу, я выбрал два очень простых примера из парыразмещает в Интернете и создал простой проект Spring-Boot, который можно клонировать и запускать самостоятельно.
https://github.com/illingtonFlex/ViewScopeDemo

Это демонстрационное приложение содержит два управляемых bean-компонента и два файла xhtml.

Первый пример - тот, который скопирован из примера на веб-сайте BalusC: http://balusc.omnifaces.org/2010/06/benefits-and-pitfalls-of-viewscoped.html

Файл xhtml выглядит следующим образом:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:h="http://java.sun.com/jsf/html">
<h:head>
    <title>Really simple CRUD</title>
</h:head>
<h:body>
    <h3>List items</h3>
    <h:form rendered="#{not empty viewScopedController.list}">
        <h:dataTable value="#{viewScopedController.list}" var="item">
            <h:column><f:facet name="header">ID</f:facet>#{item.id}</h:column>
            <h:column><f:facet name="header">Value</f:facet>#{item.value}</h:column>
            <h:column><h:commandButton value="edit" action="#{viewScopedController.doEdit(item)}" /></h:column>
            <h:column><h:commandButton value="delete" action="#{viewScopedController.delete(item)}" /></h:column>
        </h:dataTable>
    </h:form>
    <h:panelGroup rendered="#{empty viewScopedController.list}">
        <p>Table is empty! Please add new items.</p>
    </h:panelGroup>
    <h:panelGroup rendered="#{!viewScopedController.edit}">
        <h3>Add item</h3>
        <h:form>
            <p>Value: <h:inputText value="#{viewScopedController.item.value}" /></p>
            <p><h:commandButton value="add" action="#{viewScopedController.add}" /></p>
        </h:form>
    </h:panelGroup>
    <h:panelGroup rendered="#{viewScopedController.edit}">
        <h3>Edit item #{viewScopedController.item.id}</h3>
        <h:form>
            <p>Value: <h:inputText value="#{viewScopedController.item.value}" /></p>
            <p><h:commandButton value="save" action="#{viewScopedController.save}" /></p>
        </h:form>
    </h:panelGroup>
</h:body>
</html>

, и компонент ViewScoped, поддерживающий эту страницу, выглядит следующим образом:

package help.me.understand.jsf.ViewScopeDemo.controller;

import help.me.understand.jsf.ViewScopeDemo.model.Item;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.faces.view.ViewScoped;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@Component
@ViewScoped
@Data
@EqualsAndHashCode(callSuper=false)
@ToString
public class ViewScopedController implements Serializable {
    private static final Logger log = LoggerFactory.getLogger(ViewScopedController.class);
    private List<Item> list;
    private Item item = new Item();
    private boolean edit;

    @PostConstruct
    public void init() {
        // list = dao.list();
        // Actually, you should retrieve the list from DAO. This is just for demo.
        list = new ArrayList<Item>();
        list.add(new Item(1L, "item1"));
        list.add(new Item(2L, "item2"));
        list.add(new Item(3L, "item3"));
    }

    public void add() {
        // dao.create(item);
        // Actually, the DAO should already have set the ID from DB. This is just for demo.
        item.setId(list.isEmpty() ? 1 : list.get(list.size() - 1).getId() + 1);
        list.add(item);
        item = new Item(); // Reset placeholder.
    }

    public void doEdit(Item item) {
        this.item = item;
        edit = true;
    }

    public void save() {
        // dao.update(item);
        item = new Item(); // Reset placeholder.
        edit = false;
    }

    public void delete(Item item) {
        // dao.delete(item);
        list.remove(item);
    }

    public List<Item> getList() {
        return list;
    }

    public Item getItem() {
        return item;
    }

    public boolean isEdit() {
        return edit;
    }

    // Other getters/setters are actually unnecessary. Feel free to add them though.
}

Если вы запустите приложение и перейдете по адресу localhost: 8080 / index.xhtml, нажмите «Изменить» на одной из записей.Затем введите новое имя в текстовое поле и нажмите «Сохранить».Метод save для управляемого компонента никогда не вызывается, и страница «сбрасывается» в исходное состояние.Если вы отключите Hazelcast и репликацию сеанса, закомментировав аннотацию @EnableHazelcastHttpSession вместе с hazelcastInstance @Bean, определенным в ViewScopeDemoApplication, приведенные выше примеры шагов сработают.Вызывается метод сохранения, и имя редактируемого элемента изменяется.

Чтобы продемонстрировать еще один пример странного поведения ViewScoped, я скопировал дословный пример кода мастера из демонстрации PrimeFaces: https://www.primefaces.org/showcase/ui/panel/wizard.xhtml

Запустив приложение, вы можете получить доступ к этому примеру через localhost: 8080 / wizard.xhtml

При включенной Hazelcast и репликации сеансов вы можете установить точку останова в методе onFlowProcess, который срабатывает при переходе от одногоСтраница мастера на следующую.Вы можете видеть, что значения, введенные на первом шаге мастера, теряются (они становятся нулевыми) при последующих изменениях страницы мастера.Отключите Hazelcast, и значения сохранятся на всем протяжении вкладок мастера.

Я не вижу каких-либо ошибок или исключений в журналах при возникновении проблем. Также я не вижу проблем в консоли отладки браузера. Однако из этих двух примеров ясно, что bean-компоненты ViewScoped ведут себя по-разному в зависимости от того, включена ли репликация сеанса Hazelcast.

Заранее благодарим за помощь и внимание!

1 Ответ

0 голосов
/ 31 мая 2019

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

То, что сделало трюк, добавило следующее свойство в мой файл application.properties:

spring.session.servlet.filter-dispatcher-types=async, error, forward, include

Дело в том, что установка ЛЮБОГО типа диспетчера, отличного от «запроса», похоже, заставляет мои bean-компоненты ViewScoped вести себя так, как я ожидаю. Если «запрос» является одним из указанных вами типов диспетчера, странное поведение ViewScope, похоже, проявляется.

Я обновлю проект Github, упомянутый в оригинальном посте, чтобы другие могли поиграть с ним и увидеть разницу.

Надеюсь, это, по крайней мере, послужит подсказкой для кого-то еще, имеющего подобную проблему!

...