PrimeFaces动态对话框中p:dataTable提前初始化问题及方案咨询
问题原因分析
首先得明确:PrimeFaces的dynamic="true"对话框本质是延迟将内容渲染到浏览器DOM,但服务器端的JSF组件树在页面加载时就已经构建完成了。
当你把p:dataTable放进对话框后,这个组件会被直接加入到组件树中。而dataTable的value属性是值绑定表达式(#{testBean.someArrayList}),JSF在处理组件的绑定属性时,会尝试解析表达式获取值——这就会触发testBean的实例化,以及getSomeArrayList()的调用,哪怕对话框还没被打开。
而你之前用p:outputPanel的场景里,只有rendered属性的EL表达式,这个表达式的评估是在渲染阶段才进行的,不会在组件树构建阶段触发bean初始化,所以当时符合你的预期。
解决方案及对比
下面给你几种可行方案,各有优劣,你可以根据需求选择:
1. 用JSTL <c:if>控制组件树构建
JSTL的<c:if>是在视图构建阶段执行的,只有条件满足时,里面的组件才会被加入组件树。你可以在backing bean里加一个dialogVisible布尔属性,打开对话框时设为true,关闭时设为false,然后:
<p:dialog dynamic="true" closeOnEscape="true" id="modalID" modal="true"> <c:if test="#{testBean.dialogVisible}"> <p:dataTable value="#{testBean.someArrayList}" var="item"> <p:column>#{item}</p:column> </p:dataTable> </c:if> </p:dialog>
- 优点:彻底避免不必要的组件树构建和bean初始化。
- 缺点:混合JSTL和JSF确实有风险,比如AJAX请求中可能出现组件树不一致的问题,需要仔细同步状态。
2. 动态src的<ui:include>
把p:dataTable单独放到一个XHTML片段(比如dataTableFragment.xhtml),然后在对话框中动态加载:
<p:dialog dynamic="true" closeOnEscape="true" id="modalID" modal="true" onShow="PF('modalWidget').loadContent()"> <ui:include src="#{testBean.dialogContentSrc}" /> </p:dialog>
在backing bean中,默认dialogContentSrc设为空或一个空片段,打开对话框时再把它设为dataTableFragment.xhtml。
- 优点:完全分离内容,避免组件树提前构建,符合动态对话框的延迟理念,也不会有JSTL和JSF的冲突问题。
- 缺点:需要多一个XHTML文件,项目结构复杂度略有增加,但影响极小。
3. 用<p:remoteCommand>延迟加载数据
保持p:dataTable在对话框中,但初始时value绑定空集合,对话框显示时调用remoteCommand加载数据:
<p:dialog dynamic="true" closeOnEscape="true" id="modalID" modal="true" onShow="loadTableData()"> <p:dataTable value="#{testBean.someArrayList}" var="item"> <p:column>#{item}</p:column> </p:dataTable> </p:dialog> <p:remoteCommand name="loadTableData" action="#{testBean.loadSomeArrayList}" update="modalID" />
backing bean中someArrayList初始为空,loadSomeArrayList()方法负责加载实际数据。
- 优点:不需要额外文件,也不用混合JSTL,用PrimeFaces原生组件就能实现。
- 缺点:bean还是会被初始化(因为dataTable绑定了它),但数据是延迟加载的。如果你的需求是完全避免bean初始化,这个方案不适用。
4. 懒加载p:dataTable
如果数据量较大,可以结合PrimeFaces的懒加载功能:
<p:dialog dynamic="true" closeOnEscape="true" id="modalID" modal="true"> <p:dataTable value="#{testBean.lazyDataModel}" var="item" lazy="true"> <p:column>#{item}</p:column> </p:dataTable> </p:dialog>
这里lazyDataModel是LazyDataModel的实例,只有当dataTable需要渲染数据(对话框显示后),才会调用load()方法加载数据。
- 优点:适合大数据场景,原生支持延迟加载,无需额外AJAX调用。
- 缺点:bean还是会被初始化(要创建
LazyDataModel),仅延迟了实际数据的加载。
最优方案推荐
如果你的核心需求是完全避免testBean在对话框未显示时初始化,那么**动态<ui:include>**的方案副作用最小:
- 没有JSTL和JSF生命周期冲突的风险;
- 真正实现了内容的延迟加载,只有对话框显示时才会触发组件构建和bean初始化;
- 额外的项目复杂度可以忽略不计。
如果只是想避免提前加载数据,而非完全禁止bean初始化,那么<p:remoteCommand>结合延迟加载的方案更简单快捷。
内容的提问来源于stack exchange,提问作者Ben

