将Spring-Session集群与Hazelcast结合使用时,ViewScoped Bean出现意外行为

时间:2019-05-28 18:42:03

标签: primefaces hazelcast spring-session jsf-2.3 joinfaces

我正在将群集引入基于JSF的Spring-Boot Web应用程序中,一旦我们启用了Hazelcast的会话复制功能,我们就开始注意到我们的一些使用ViewScoped bean的JSF页面不再正常运行。如果我们禁用会话复制和Hazelcast,则不再发生奇怪的行为。

我首先在使用PrimeFaces向导组件的页面中注意到了这个问题。当第二页“提交”时,在向导第一页上输入的值将丢失。

然后在另一个页面上,我注意到命令按钮不再调用托管bean上的actionListener方法。我在方法中设置了一个断点,但断点从未被命中,但是页面“闪烁”并刷新回到其初始状态。我确实注意到,托管bean上的PostConstruct方法不会再次调用,因此它不会生成ViewScoped bean的新实例。

但是,当我禁用会话复制和Hazelcast时,这些问题都没有发生。据我所知,检查会话及其内容,看起来确实像正在创建会话并正确存储会话一样。

该应用程序是一个Spring-Boot Web应用程序,使用joinfaces入门工具引入了JSF 2.3.7(Mojarra),PrimeFaces 6.2和Omnifaces 1.14.1。我们最初开发的应用程序没有进行任何会话复制,而且ViewScoped bean没有任何问题。

ViewScoped bean正在使用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 bean看起来像这样:

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,请在其中一项上单击“编辑”。然后在文本字段中输入新名称,然后单击“保存”。永远不会调用托管bean上的save方法,并且页面会“重置”为其初始状态。如果通过注释掉@EnableHazelcastHttpSession注释以及在ViewScopeDemoApplication中定义的hazelcastInstance @Bean来禁用Hazelcast和会话复制,则上述示例步骤将起作用。调用save方法,并更改已编辑项目的名称。

为了演示另一个奇怪的ViewScoped行为示例,我从PrimeFaces展示区逐字复制了向导示例代码: https://www.primefaces.org/showcase/ui/panel/wizard.xhtml

启动应用后,您可以通过以下方式访问此示例 本地主机:8080 / wizard.xhtml

启用Hazelcast和会话复制后,可以在onFlowProcess方法中设置一个断点,该方法在从向导的一页导航到下一页时将触发。您可以看到,在向导的第一步中输入的值在后续的向导页面更改中会丢失(它们变为null)。禁用Hazelcast,值将在“向导”选项卡的整个范围内保持不变。

发生问题时,我在日志中没有看到任何错误或异常。同样,在浏览器调试控制台中也看不到任何问题。但是,从这两个示例可以清楚地看出,取决于是否启用了Hazelcast会话复制,ViewScoped bean的行为有所不同。

在此先感谢您的帮助和考虑!

1 个答案:

答案 0 :(得分:0)

我似乎偶然发现了ViewScoped问题的解决方案。我承认我还不完全了解这是如何造成的,但是我想我会为将来可能遇到此职位的其他人发布解决方案。希望有人比我更聪明,可以帮助我理解为什么这样做,并指出如果有更好的解决方案,为什么它不是一个好主意。

达到目的的是在我的application.properties文件中添加了以下属性:

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

问题是,设置除“ request”以外的任何调度程序类型似乎都会导致ViewScoped bean的行为符合我的期望。如果“请求”是您指定的调度程序类型之一,则ViewScope行为似乎很奇怪。

我将更新原始帖子中提到的Github项目,以便其他人可以使用它并看到其中的区别。

希望这至少可以为其他有类似问题的人提供线索!