为什么getter会被渲染的属性多次调用?
与前面的例子相关,我试图监视服务器上的get / set方法(当它们被调用时,以及多久)。 所以,我的实际看起来是这样的:
@ManagedBean(name="selector") @RequestScoped public class Selector { @ManagedProperty(value="#{param.profilePage}") private String profilePage; public String getProfilePage() { if(profilePage==null || profilePage.trim().isEmpty()) { this.profilePage="main"; } System.out.println("GET "+profilePage); return profilePage; } public void setProfilePage(String profilePage) { this.profilePage=profilePage; System.out.println("SET "+profilePage); } }
和唯一的页面谁可以调用这个方法(它只调用get方法渲染)是:
<!DOCTYPE html> <ui:composition xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:ui="http://java.sun.com/jsf/facelets"> <h:panelGroup layout="block" id="profileContent"> <h:panelGroup rendered="#{selector.profilePage=='main'}"> // nothing at the moment </h:panelGroup> </h:panelGroup> </ui:composition>
当我看到服务器日志时,我的木偶,我看到:
SET null GET main GET main GET main GET main GET main GET main GET main
什么? 它调用getProfilePage()
方法的七倍? (还有1次setProfilePage()
)我想知道为什么这个行为:)
谢谢
添加了一个例子
豆
@ManagedBean(name="selector") @RequestScoped public class Selector { @ManagedProperty(value="#{param.profilePage}") private String profilePage; @PostConstruct public void init() { if(profilePage==null || profilePage.trim().isEmpty()) { this.profilePage="main"; } } public String getProfilePage() { return profilePage; } public void setProfilePage(String profilePage) { this.profilePage=profilePage; } }
profile.xhtml
<h:panelGroup layout="block" id="profileContent"> <h:panelGroup layout="block" styleClass="content_title"> Profilo Utente </h:panelGroup> <h:panelGroup rendered="#{selector.profilePage=='main'}"> <ui:include src="/profile/profile_main.xhtml" /> </h:panelGroup> <h:panelGroup rendered="#{selector.profilePage=='edit'}"> <ui:include src="/profile/profile_edit.xhtml" /> </h:panelGroup> </h:panelGroup> // profile_main.xhtml <h:form id="formProfileMain" prependId="false"> <h:panelGroup layout="block" styleClass="content_span"> <h:outputScript name="jsf.js" library="javax.faces" target="head" /> <h:panelGroup layout="block" styleClass="profilo_3"> <h:commandButton value="EDIT"> <f:setPropertyActionListener target="#{selector.profilePage}" value="edit" /> <f:ajax event="action" render=":profileContent"/> </h:commandButton> </h:panelGroup> </h:panelGroup> </h:form> // profile_edit.xhtml <h:form id="formProfileEdit" prependId="false"> <h:panelGroup layout="block" styleClass="content_span"> <h:outputScript name="jsf.js" library="javax.faces" target="head" /> <h:panelGroup layout="block" styleClass="profilo_3"> <h:commandButton value="Edit"> <f:setPropertyActionListener target="#{selector.profilePage}" value="editProfile" /> <f:ajax event="action" render=":profileContent"/> </h:commandButton> <h:commandButton value="Back"> <f:setPropertyActionListener target="#{selector.profilePage}" value="main" /> <f:ajax event="action" render=":profileContent"/> </h:commandButton> </h:panelGroup> </h:panelGroup> </h:form>
在这个例子中,我调用了profile_main(默认)。 之后(例如)我打电话profile_edit(通过点击编辑); 之后,我通过单击返回返回到profile_main。 现在,如果我想重新加载profile_edit(编辑),我需要点击该命令button上多次。 为什么?
EL(Expression Language,那些#{}
东西)不会caching调用的结果等等。 它只是直接访问bean中的数据。 如果getter只是返回数据,这通常不会造成伤害。
setter调用由@ManagedProperty
完成。 它基本上做到以下几点:
selector.setProfilePage(request.getParameter("profilePage"));
在渲染响应阶段,getter调用全部通过rendered="#{selector.profilePage == 'some'}"
。 当它在第一次评估false
时,在UIComponent#encodeAll()
,将不再有呼叫。 当评估结果为true
,将按以下顺序重新评估六次:
-
UIComponent#encodeBegin()
– find组件开始的渲染器。 -
Renderer#encodeBegin()
– 呈现组件的开始。 -
UIComponent#encodeChildren()
– find组件的子项的渲染器。 -
Renderer#encodeChildren()
– 渲染组件的子项。 -
UIComponent#encodeEnd()
– find组件结束的渲染器。 -
Renderer#encodeEnd()
– 渲染组件的结尾。
组件及其渲染器在每个步骤中validation是否允许渲染。 在表单提交期间,如果input或命令组件或其任何父母具有rendered
属性,则在应用请求值阶段也将对其进行评估,作为防范篡改/被黑客请求的一部分。
诚然,这看起来笨拙和低效率。 根据规范问题941,它被认为是JSF的跟腱愈合。 build议删除所有那些重复的检查,并坚持在UIComponent#encodeAll()
完成的检查,或者在每个阶段基础上评估isRendered()
。 在EG讨论中 ,问题的根源在于EL,而不是在JSF中,CDI可以大大提高性能。 所以没有必要从JSF规范的angular度来解决它。
如果您担心托pipe属性在设置后只应检查一次(如果它为空或空),则考虑将其移动到使用@PostConstruct
注释的方法中。 这种方法将在bean的构build和所有dependency injection之后被直接调用。
@PostConstruct public void init() { if (profilePage == null || profilePage.trim().isEmpty()) { profilePage = "main"; } }
也可以看看:
- 为什么JSF多次调用getters?
你可以使用CDI Producer方法。 它会被调用很多次,但是第一次调用的结果被caching在bean的作用域中,并且对于正在计算或初始化重对象的getter来说是有效的! 在这里看到更多的信息。