表单内使用<ui:include/>的PrimeFaces对话框中,实现JSF Backing Bean随对话框开闭重建与销毁的问题
这个场景我之前也遇到过——视图作用域(View Scope)是绑定整个页面生命周期的,对话框只是页面的一部分,关闭它根本不会触发视图销毁,所以@PreDestroy自然不会执行。下面给你几个实用的解决方案,按实现复杂度从低到高排序:
方案1:利用PrimeFaces对话框的销毁事件手动清理Bean
这个方案最简单,不需要改太多代码,靠对话框自带的属性和事件就能实现:
步骤1:修改对话框配置
给你的p:dialog加上destroyOnClose="true"和closeListener,让对话框关闭时不仅销毁自身组件,还触发Bean清理:
<h:form id="myForm"> <p:dialog header="Title" id="someDialog" widgetVar="someDialog" modal="true" resizable="false" closable="true" destroyOnClose="true" closeListener="#{myController.onDialogClose}"> <ui:include src="/WEB-INF/includes/myInclude.xhtml"/> </p:dialog> </h:form>
步骤2:更新Bean逻辑
把Bean的作用域改成JSF原生的@ViewScoped(或者PrimeFaces的org.primefaces.view.ViewScoped,适配不同版本),然后添加关闭事件的处理方法,手动从视图Map中移除Bean实例:
@Component @ViewScoped public class MyController implements Serializable { private static final Logger LOGGER = LoggerFactory.getLogger(MyController.class); @PostConstruct private void postConstruct(){ LOGGER.info("post construct"); } @PreDestroy private void preDestroy(){ LOGGER.info("pre destroy"); } public void test(){ LOGGER.info("test"); PrimeFaces.current().executeScript("PF('someDialog').hide()"); } public void onDialogClose(CloseEvent event) { // 从视图根的Map中移除当前Bean,下次打开对话框会重新实例化 FacesContext.getCurrentInstance().getViewRoot().getViewMap().remove("myController"); } }
原理:destroyOnClose="true"会让PrimeFaces销毁对话框的组件树,而我们手动移除视图Map里的Bean实例,下次对话框渲染时,JSF发现Bean不存在就会重新创建,完美匹配对话框的生命周期。
方案2:用Spring对话作用域绑定对话框生命周期
如果你的项目依赖Spring,@ConversationScoped是更优雅的选择,它天生就是用来绑定一段“会话流程”的,正好对应对话框的打开到关闭:
步骤1:配置Spring对话作用域(如果没配置过)
在你的Spring配置类里添加这个Bean,开启对话作用域支持:
@Bean public Scope conversationScope() { return new ConversationScope(); }
步骤2:改造Bean逻辑
把作用域改成@ConversationScoped,然后在Bean初始化时开启对话,关闭对话框时结束对话:
@Component @ConversationScoped public class MyController implements Serializable { private static final Logger LOGGER = LoggerFactory.getLogger(MyController.class); @Autowired private Conversation conversation; @PostConstruct private void postConstruct(){ conversation.begin(); // 打开对话框时启动对话 LOGGER.info("post construct"); } @PreDestroy private void preDestroy(){ LOGGER.info("pre destroy"); } public void test(){ LOGGER.info("test"); conversation.end(); // 点击按钮关闭时结束对话,触发Bean销毁 PrimeFaces.current().executeScript("PF('someDialog').hide()"); } public void onDialogClose(CloseEvent event) { // 处理用户手动关闭对话框的情况,确保对话结束 if (!conversation.isTransient()) { conversation.end(); } } }
步骤3:给对话框添加关闭监听
和方案1一样,给p:dialog加上closeListener="#{myController.onDialogClose}",确保用户手动关闭对话框时也能结束对话,销毁Bean。
原理:对话作用域的Bean会在conversation.begin()时创建,conversation.end()时销毁,完全和对话框的打开/关闭流程绑定,不需要手动清理视图Map,更符合Spring的设计思想。
方案3:自定义对话框作用域(进阶)
如果上面两个方案都不满足你的需求,你可以自定义一个Spring作用域,专门绑定PrimeFaces对话框的生命周期。不过这个实现比较复杂,需要处理对话框的打开、关闭事件,以及请求时的Bean实例获取,适合有自定义作用域开发经验的同学,这里就不展开细节了。
测试验证
不管用哪个方案,测试流程都是:
- 打开对话框:控制台输出
post construct; - 点击Test按钮:输出
test,对话框关闭,同时触发@PreDestroy输出pre destroy; - 再次打开对话框:重新输出
post construct,Bean被全新实例化。
内容的提问来源于stack exchange,提问作者Deffa

