我正在努力将集群引入基于 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
启动应用程序后,您可以通过 localhost:8080/wizard.xhtml 访问此示例
启用 Hazelcast 和会话复制后,您可以在 onFlowProcess 方法中设置一个断点,该断点在从向导的一个页面导航到下一个页面时触发。您可以看到在向导第一步中输入的值在随后的向导页面更改中丢失(它们变为空)。禁用 Hazelcast,值会在整个向导选项卡范围内保持不变。
当问题发生时,我没有在日志中看到任何错误或异常。我也没有在浏览器调试控制台中看到任何问题。但是,从这两个示例中可以清楚地看出,ViewScoped bean 的行为取决于是否启用了 Hazelcast 会话复制。
提前感谢您的帮助和考虑!