Primefaces 将Spring会话群集与Hazelcast一起使用时,ViewScope bean出现意外行为

Primefaces 将Spring会话群集与Hazelcast一起使用时,ViewScope bean出现意外行为,primefaces,hazelcast,spring-session,jsf-2.3,joinfaces,Primefaces,Hazelcast,Spring Session,Jsf 2.3,Joinfaces,我正在致力于将集群引入基于JSF的Spring Boot web应用程序,当我们使用Hazelcast启用会话复制时,我们开始注意到我们使用ViewScoped bean的几个JSF页面不再正常工作。如果我们禁用会话复制和Hazelcast,这种奇怪的行为将不再发生 我首先在一个使用PrimeFaces向导组件的页面中注意到了这个问题。当第二页“提交”时,向导第一页上输入的值将丢失 然后在另一个页面上,我注意到一个命令按钮不再调用托管bean上的actionListener方法。我在方法中设置了

我正在致力于将集群引入基于JSF的Spring Boot web应用程序,当我们使用Hazelcast启用会话复制时,我们开始注意到我们使用ViewScoped bean的几个JSF页面不再正常工作。如果我们禁用会话复制和Hazelcast,这种奇怪的行为将不再发生

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

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

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

该应用程序是一个SpringBootWeb应用程序,使用joinfaces启动器引入JSF2.3.7(Mojarra)、PrimeFaces 6.2和Omnifaces 1.14.1。我们最初开发应用程序时没有任何会话复制,我们的ViewScope bean也没有问题

ViewScope bean使用org.springframework.stereotype.Component注释,就像您在joinfaces示例中看到的那样,并将javax.faces.view.ViewScope作为范围注释。我还尝试引入Weld并使用@Named注释,以及使用旧的不推荐使用的JSF@ManagedBean和@viewscope注释,但在所有情况下都存在相同的行为

我已经检查并确保我们的ManagedBeans以及Bean本身的任何属性都是完全可序列化的

为了演示我所看到的情况,我从web上的几个地方选取了两个非常简单的示例,并创建了一个简单的Spring Boot项目,您可以自己克隆并运行它。

这个演示应用程序包含两个托管bean和两个xhtml文件

第一个示例是从BalusC网站上的示例复制的:

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>
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.
}

非常简单的积垢
清单项目
ID#{item.ID}
值#{item.Value}
桌子空了!请添加新项目

添加项 价值:

编辑项#{viewScopedController.item.id} 价值:

支持此页面的ViewScope bean如下所示:

<!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>
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.
}
包help.me.understand.jsf.ViewScopeDemo.controller;
导入help.me.understand.jsf.ViewScopeDemo.model.Item;
导入龙目数据;
导入lombok.EqualsAndHashCode;
进口龙目草;
导入org.slf4j.Logger;
导入org.slf4j.LoggerFactory;
导入org.springframework.stereotype.Component;
导入javax.annotation.PostConstruct;
导入javax.faces.view.ViewScoped;
导入java.io.Serializable;
导入java.util.ArrayList;
导入java.util.List;
@组成部分
@视域
@资料
@EqualsAndHashCode(callSuper=false)
@托斯特林
公共类ViewScopedController实现可序列化{
私有静态最终记录器log=LoggerFactory.getLogger(ViewScopedController.class);
私人名单;
私有项=新项();
私有布尔编辑;
@施工后
公共void init(){
//list=dao.list();
//实际上,您应该从DAO中检索列表。这只是为了演示。
列表=新的ArrayList();
增加(新项目(1L,“项目1”);
添加(新项目(2L,“项目2”);
增加(新项目(3L,“项目3”);
}
公共无效添加(){
//创建(项目);
//实际上,DAO应该已经从DB设置了ID。这只是为了演示。
item.setId(list.isEmpty()?1:list.get(list.size()-1.getId()+1);
列表。添加(项目);
item=new item();//重置占位符。
}
公共无效数据编辑(项目){
this.item=项目;
编辑=真;
}
公共作废保存(){
//更新(项目);
item=new item();//重置占位符。
编辑=假;
}
公共作废删除(项目){
//删除(项目);
列表。删除(项目);
}
公共列表getList(){
退货清单;
}
公共项getItem(){
退货项目;
}
公共布尔isEdit(){
返回编辑;
}
//其他的getter/setter实际上是不必要的。尽管可以随意添加它们。
}
如果启动应用程序并导航到localhost:8080/index.xhtml,请单击其中一个条目上的编辑。然后在文本字段中输入新名称并单击保存。托管bean上的save方法永远不会被调用,页面会“重置”到其初始状态。如果您通过注释掉@EnableHazelcastHttpSession注释以及ViewScopeDemoApplication中定义的hazelcastInstance@Bean来禁用Hazelcast和会话复制,则上面的示例步骤可以工作。调用save方法,并更改已编辑项的名称

为了演示另一个奇怪的ViewScope行为示例,我从PrimeFaces showcase复制了向导示例代码:

应用程序启动后,您可以通过 localhost:8080/wizard.xhtml

启用Hazelcast和会话复制后,可以在onFlowProcess方法中设置断点,该方法在从向导的一个页面导航到下一个页面时触发。您可以看到在向导的第一步中输入的值