JSF 2中复合组件方法早于preRenderView触发的问题咨询
我来帮你拆解这个问题——你遇到的情况其实是JSF生命周期的正常行为,但确实和你的预期冲突,咱们先搞清楚背后的逻辑,再给你可行的解决办法:
为什么会出现这个问题?
JSF的视图处理分为几个核心阶段,关键顺序是:
- 视图构建阶段(View Build Time):JSF会解析你的xhtml页面,构建组件树,这时候会解析页面中所有的EL表达式(比如你复合组件里的
#{view.actions}),触发对应的getter方法来获取数据。 - 渲染响应阶段(Render Response):视图构建完成后,才会触发
preRenderView事件,然后才开始渲染组件输出HTML。
你的复合组件里的<ui:repeat>会在视图构建阶段就去读取#{view.actions},这时候preRenderView绑定的#{view.init}还没执行呢——因为preRenderView是视图构建完成后才触发的,自然就会出现你看到的“复合组件方法先执行”的情况。
解决方法
根据你的需求,有几种不同的方案可选:
方案1:把初始化逻辑移到更早的时机(推荐)
如果你的init方法是用来初始化view.actions这类数据的,那preRenderView其实不是合适的时机。你应该用@PostConstruct注解来标记初始化方法,它会在托管Bean实例化、依赖注入完成后立即执行,这个时机早于视图构建阶段,这样复合组件解析EL的时候,数据已经准备好了。
示例代码(你的View托管Bean):
import javax.annotation.PostConstruct; import javax.faces.bean.ManagedBean; import javax.faces.bean.ViewScoped; @ManagedBean(name = "view") @ViewScoped public class ViewBean { private List<Action> actions; private TimeZone timeZone; @PostConstruct public void init() { // 原来放在preRenderView里的初始化逻辑 this.actions = loadActionsFromDatabase(); this.timeZone = TimeZone.getDefault(); } // 省略getter/setter }
然后页面里可以直接去掉<f:event type="preRenderView" listener="#{view.init}" />,因为初始化已经提前完成了。
方案2:推迟复合组件的EL解析到渲染阶段
如果你一定要保留preRenderView的初始化逻辑,可以通过延迟复合组件内部内容的渲染时机,让它等到preRenderView执行后再去解析EL。最简单的方式是用<ui:fragment>包裹复合组件的内容,并设置rendered="#{true}"——这个属性的EL会在渲染阶段才解析,从而推迟内部组件的EL解析时机。
修改后的复合组件实现:
<composite:implementation> <ui:fragment rendered="#{true}"> <h:form id="tableForm"> <ui:repeat value="#{view.actions}" var="action"> <h:outputText value="#{action.start}"> <f:convertDateTime timeZone="#{view.timeZone}" /> </h:outputText> </ui:repeat> </h:form> </ui:fragment> </composite:implementation>
这样<ui:repeat>会等到preRenderView执行完成后,才去读取#{view.actions},数据就已经初始化好了。
方案3:使用视图参数或其他初始化机制
如果你的初始化逻辑依赖请求参数,也可以考虑用<f:viewParam>来绑定参数并触发初始化,它的执行时机也早于视图构建阶段,同样能解决问题。
内容的提问来源于stack exchange,提问作者matthias

