与前面的示例相关,我试图在服务器上监视我的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(通过点击编辑);之后,我通过单击Back返回profile_main。现在,如果我想重新加载profile_edit(EDIT),我需要在该命令按钮上多次单击。为什么呢?
答案 0 :(得分:34)
EL(表达式语言,那些#{}
个东西)不会缓存调用的结果左右。它只是直接访问bean中的数据。如果getter只是返回数据,这通常不会造成伤害。
setter调用由@ManagedProperty
完成。它基本上做了以下几点:
selector.setProfilePage(request.getParameter("profilePage"));
在渲染响应阶段,getter调用全部由rendered="#{selector.profilePage == 'some'}"
完成。当它第一次评估false
时,在UIComponent#encodeAll()
中,则不会再进行任何调用。当它评估true
时,它将按以下顺序重新评估六次:
UIComponent#encodeBegin()
- 找到组件开头的渲染器。Renderer#encodeBegin()
- 渲染组件的开头。UIComponent#encodeChildren()
- 找到组件子项的渲染器。Renderer#encodeChildren()
- 呈现组件的子项。UIComponent#encodeEnd()
- 找到渲染器以获取组件结尾。Renderer#encodeEnd()
- 渲染组件结尾。组件及其渲染器在每个步骤中验证是否允许渲染。在表单提交期间,如果输入或命令组件或其任何父组件具有rendered
属性,那么它也将在应用请求值阶段进行评估,作为防止篡改/被黑客请求的一部分。
没错,这看起来很笨拙,效率低下。根据{{3}},它被认为是JSF的致命弱点。建议删除所有这些重复检查并坚持在UIComponent#encodeAll()
中完成的检查,或者在每个阶段评估isRendered()
。 spec issue 941,很明显问题的根源在于EL,而不是在JSF中,并且使用CDI可以大大提高性能。所以没有必要从JSF规范方面解决它。
如果您担心托管属性在设置后只应检查一次,如果它为null或为空,则考虑将其移动到使用@PostConstruct
注释的方法中。在bean的构造和所有依赖注入之后,将直接调用这样的方法。
@PostConstruct
public void init() {
if (profilePage == null || profilePage.trim().isEmpty()) {
profilePage = "main";
}
}
答案 1 :(得分:3)
您可以使用CDI Producers方法。 它将被多次调用,但第一次调用的结果缓存在bean的范围内,对于计算或初始化重物的getter是有效的! 有关详细信息,请参阅here。