为什么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 ,将按以下顺序重新评估六次:

  1. UIComponent#encodeBegin() – find组件开始的渲染器。
  2. Renderer#encodeBegin() – 呈现组件的开始。
  3. UIComponent#encodeChildren() – find组件的子项的渲染器。
  4. Renderer#encodeChildren() – 渲染组件的子项。
  5. UIComponent#encodeEnd() – find组件结束的渲染器。
  6. 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来说是有效的! 在这里看到更多的信息。

Interesting Posts