我正在将群集引入基于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的行为有所不同。
在此先感谢您的帮助和考虑!
答案 0 :(得分:0)
我似乎偶然发现了ViewScoped问题的解决方案。我承认我还不完全了解这是如何造成的,但是我想我会为将来可能遇到此职位的其他人发布解决方案。希望有人比我更聪明,可以帮助我理解为什么这样做,并指出如果有更好的解决方案,为什么它不是一个好主意。
达到目的的是在我的application.properties文件中添加了以下属性:
spring.session.servlet.filter-dispatcher-types=async, error, forward, include
问题是,设置除“ request”以外的任何调度程序类型似乎都会导致ViewScoped bean的行为符合我的期望。如果“请求”是您指定的调度程序类型之一,则ViewScope行为似乎很奇怪。
我将更新原始帖子中提到的Github项目,以便其他人可以使用它并看到其中的区别。
希望这至少可以为其他有类似问题的人提供线索!