我正在努力将集群引入基于JSF的Spring-Boot Web应用程序,一旦我们使用Hazelcast启用会话复制,我们开始注意到我们的几个使用ViewScoped bean的JSF页面不再正常运行。如果我们禁用会话复制和Hazelcast,奇怪的行为将不再发生。
我第一次注意到这个问题是在我们的一个使用PrimeFaces向导组件的页面中。当第二页“提交”时,在向导第一页输入的值会丢失。
然后在另一个页面上,我注意到一个命令按钮不再调用托管bean上的actionListener方法。我在方法中设置了一个断点,断点永远不会被命中,但是页面“闪烁”并刷新回其初始状态。我确实注意到托管bean上的PostConstruct方法不会再次调用,因此它不会生成ViewScoped bean的新实例。
但是,当我禁用会话复制和Hazelcast时,这些问题都不会发生。据我所知,检查会话及其内容,它看起来确实像会话正在创建并正确存储,据我所知。
该应用程序是一个Spring-Boot Web应用程序,使用连接面启动器引入JSF 2.3.7(Mojarra)、PrimeFaces 6.2和Omniface 1.14.1。我们最初开发该应用程序时没有任何会话复制,我们的ViewScoped bean也没有问题。
ViewScoped bean使用的是org. springframe.的原型.Component注释,就像你在连接面示例中看到的那样,以及javax.faces.view.ViewScoped作为作用域注释。我还尝试引入Weld并使用@命名注释以及回退到旧的已弃用的JSF@ManagedBean和@ViewScoped注释,但在所有情况下都存在相同的行为。
我已经检查并确保我们的ManagedBean都是完全可序列化的,以及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上的保存方法永远不会被调用,页面将“重置”到其初始状态。如果您通过注释掉@EnableHazelcastHttpSession注释以及ViewScopeDemoApplication中定义的hazelcastInstance@Bean来禁用Hazelcast和会话复制,则上述示例步骤有效。调用保存方法,并更改编辑项的名称。
为了演示另一个奇怪的ViewScoped行为的例子,我从PrimeFaces展示中逐字复制了向导示例代码:https://www.primefaces.org/showcase/ui/panel/wizard.xhtml
启动应用后,您可以通过localhost:8080/wizard.xhtml访问此示例
启用Hazelcast和会话复制后,您可以在onFlowProcess方法中设置断点,该方法在从向导的一页导航到下一页时触发。您可以看到在向导的第一步输入的值在随后的向导页面更改中丢失(它们变为null)。禁用Hazelcast,这些值将在向导选项卡的整个范围内保持不变。
当问题发生时,我没有在日志中看到任何错误或任何类型的异常。我也没有在浏览器调试控制台中看到任何问题。然而,从这两个例子中可以清楚地看出,根据是否启用Hazelcast会话复制,ViewScoped bean的行为不同。
提前感谢您的帮助和考虑!
我似乎偶然发现了一个解决我的ViewScoped问题的方法。我承认我还不完全理解这是如何产生影响的,但是我想我会为将来可能遇到这篇文章的其他人发布一个解决方案。希望有比我更聪明的人能来帮我理解为什么这个方法有效,并可能指出为什么如果有更好的解决方案,这不是一个好主意。
这样做的诀窍是添加以下属性到我的application.properties文件:
spring.session.servlet.filter-dispatcher-types=async, error, forward, include
问题是,设置除“请求”之外的任何调度程序类型似乎会导致我的ViewScoped bean按照我期望的方式运行。如果“请求”是您指定的调度程序类型之一,那么奇怪的ViewScope行为似乎会显现出来。
我将更新原始帖子中提到的Github项目,以便其他人可以玩它并看到差异。
希望这至少能为其他有类似问题的人提供线索!