提问人:Sevas 提问时间:1/19/2010 最后编辑:BalusCSevas 更新时间:4/17/2022 访问量:105337
为什么 JSF 多次调用 getter
Why JSF calls getters multiple times
问:
假设我指定了一个 outputText 组件,如下所示:
<h:outputText value="#{ManagedBean.someProperty}"/>
如果我在调用 getter for 并加载页面时打印日志消息,那么很容易注意到每个请求多次调用 getter(在我的情况下发生了两次或三次):someProperty
DEBUG 2010-01-18 23:31:40,104 (ManagedBean.java:13) - Getting some property
DEBUG 2010-01-18 23:31:40,104 (ManagedBean.java:13) - Getting some property
如果 的值计算成本很高,这可能是一个问题。someProperty
我用谷歌搜索了一下,发现这是一个已知问题。一种解决方法是包括一个检查,看看它是否已经计算出来了:
private String someProperty;
public String getSomeProperty() {
if (this.someProperty == null) {
this.someProperty = this.calculatePropertyValue();
}
return this.someProperty;
}
这样做的主要问题是你得到了大量的样板代码,更不用说你可能不需要的私有变量了。
这种方法有哪些替代方案?有没有办法在没有那么多不必要的代码的情况下实现这一点?有没有办法阻止 JSF 以这种方式行事?
感谢您的输入!
答:
你可以使用 AOP 来创建某种 Aspect,在可配置的时间内缓存我们的 getter 的结果。这样可以防止您需要在数十个访问器中复制和粘贴样板代码。
评论
这是由延迟表达式的性质引起的(请注意,当使用 Facelets 而不是 JSP 时,“传统”标准表达式的行为完全相同)。延迟表达式不会立即计算,而是创建为 ValueExpression 对象,并且每次代码调用 ValueExpression#getValue()
时都会执行表达式后面的 getter 方法。#{}
${}
这通常会在每个 JSF 请求-响应周期内调用一到两次,具体取决于该组件是输入组件还是输出组件(在此处了解)。但是,当用于迭代 JSF 组件(例如 和 )时,或者在布尔表达式(如属性)中,此计数可能会(大大)更高。JSF(特别是 EL)根本不会缓存 EL 表达式的计算结果,因为它可能会在每次调用时返回不同的值(例如,当它依赖于当前迭代的数据表行时)。<h:dataTable>
<ui:repeat>
rendered
计算 EL 表达式和调用 getter 方法是一种非常便宜的操作,因此您通常完全不必担心这一点。但是,当您出于某种原因在 getter 方法中执行昂贵的 DB/业务逻辑时,情况会发生变化。每次都会重新执行!
JSF 支持 Bean 中的 getter 方法应该设计成这样,它们只返回已经准备好的属性,仅返回其他属性,完全按照 Javabeans 规范。他们根本不应该做任何昂贵的数据库/业务逻辑。为此,应使用 bean 和/或(action)listener 方法。在基于请求的 JSF 生命周期的某个时间点,它们只执行一次,这正是您想要的。@PostConstruct
以下是预设/加载属性的所有不同正确方法的摘要。
public class Bean {
private SomeObject someProperty;
@PostConstruct
public void init() {
// In @PostConstruct (will be invoked immediately after construction and dependency/property injection).
someProperty = loadSomeProperty();
}
public void onload() {
// Or in GET action method (e.g. <f:viewAction action>).
someProperty = loadSomeProperty();
}
public void preRender(ComponentSystemEvent event) {
// Or in some SystemEvent method (e.g. <f:event type="preRenderView">).
someProperty = loadSomeProperty();
}
public void change(ValueChangeEvent event) {
// Or in some FacesEvent method (e.g. <h:inputXxx valueChangeListener>).
someProperty = loadSomeProperty();
}
public void ajaxListener(AjaxBehaviorEvent event) {
// Or in some BehaviorEvent method (e.g. <f:ajax listener>).
someProperty = loadSomeProperty();
}
public void actionListener(ActionEvent event) {
// Or in some ActionEvent method (e.g. <h:commandXxx actionListener>).
someProperty = loadSomeProperty();
}
public String submit() {
// Or in POST action method (e.g. <h:commandXxx action>).
someProperty = loadSomeProperty();
return "outcome";
}
public SomeObject getSomeProperty() {
// Just keep getter untouched. It isn't intented to do business logic!
return someProperty;
}
}
请注意,您不应该将 Bean 的构造函数或初始化块用于作业,因为如果您使用的是使用代理(如 CDI)的 Bean 管理框架,则可能会多次调用它。
如果由于一些限制性设计要求,你真的没有其他方法,那么你应该在 getter 方法中引入延迟加载。即,如果属性是 ,则加载并将其分配给属性,否则返回它。null
public SomeObject getSomeProperty() {
// If there are really no other ways, introduce lazy loading.
if (someProperty == null) {
someProperty = loadSomeProperty();
}
return someProperty;
}
这样一来,昂贵的数据库/业务逻辑就不会在每次 getter 调用时不必要地执行。
另请参阅:
- 为什么 render 属性会调用 getter 这么多次?
- 在页面装入时调用 JSF 受管 Bean 操作
- 我应该如何以及何时从数据库加载 h:dataTable 的模型
- 如何从数据库中填充h:selectOneMenu的选项?
- 使用 p:graphicImage 和 StreamedContent 显示数据库中的动态图像
- 在 JSF 页面中定义和重用 EL 变量
- 测量服务器请求后 JSF 视图的呈现时间
评论
FacesContext#getCurrentPhaseId()
如果 someProperty 的值为 计算成本高昂,这可以 可能是一个问题。
这就是我们所说的过早优化。在极少数情况下,探查器会告诉您属性的计算非常昂贵,以至于调用它三次而不是一次会对性能产生重大影响,您可以按照描述添加缓存。但是,除非你做了一些非常愚蠢的事情,比如分解素数或在 getter 中访问数据库,否则你的代码很可能在你从未想过的地方有十几个更糟糕的低效率。
评论
使用 JSF 2.0,您可以将侦听器附加到系统事件
<h:outputText value="#{ManagedBean.someProperty}">
<f:event type="preRenderView" listener="#{ManagedBean.loadSomeProperty}" />
</h:outputText>
或者,您可以将 JSF 页面包含在标记中f:view
<f:view>
<f:event type="preRenderView" listener="#{ManagedBean.loadSomeProperty}" />
.. jsf page here...
<f:view>
我写了一篇关于如何使用 Spring AOP 缓存 JSF bean getter 的文章。
我创建了一个简单方法,它拦截了所有用特殊注释注释的方法:MethodInterceptor
public class CacheAdvice implements MethodInterceptor {
private static Logger logger = LoggerFactory.getLogger(CacheAdvice.class);
@Autowired
private CacheService cacheService;
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
String key = methodInvocation.getThis() + methodInvocation.getMethod().getName();
String thread = Thread.currentThread().getName();
Object cachedValue = cacheService.getData(thread , key);
if (cachedValue == null){
cachedValue = methodInvocation.proceed();
cacheService.cacheData(thread , key , cachedValue);
logger.debug("Cache miss " + thread + " " + key);
}
else{
logger.debug("Cached hit " + thread + " " + key);
}
return cachedValue;
}
public CacheService getCacheService() {
return cacheService;
}
public void setCacheService(CacheService cacheService) {
this.cacheService = cacheService;
}
}
此拦截器用于 spring 配置文件:
<bean id="advisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<bean class="org.springframework.aop.support.annotation.AnnotationMatchingPointcut">
<constructor-arg index="0" name="classAnnotationType" type="java.lang.Class">
<null/>
</constructor-arg>
<constructor-arg index="1" value="com._4dconcept.docAdvance.jsfCache.annotation.Cacheable" name="methodAnnotationType" type="java.lang.Class"/>
</bean>
</property>
<property name="advice">
<bean class="com._4dconcept.docAdvance.jsfCache.CacheAdvice"/>
</property>
</bean>
希望能有所帮助!
这在 JSF 中仍然是一个大问题。例如,如果您有一个用于安全检查的方法,并且在您看来,您拥有该方法,那么该方法将被多次调用。isPermittedToBlaBla
rendered="#{bean.isPermittedToBlaBla}
安全检查可能很复杂,例如。LDAP查询等因此,您必须避免这种情况
Boolean isAllowed = null ... if(isAllowed==null){...} return isAllowed?
并且您必须确保在会话 Bean 中每个请求都这样做。
Ich 认为 JSF 必须在这里实现一些扩展以避免多次调用(例如,注释在阶段之后仅调用此方法一次......@Phase(RENDER_RESPONSE)
RENDER_RESPONSE
评论
如果您使用的是 CDI,则可以使用 Producers 方法。 它将被调用很多次,但第一次调用的结果被缓存在 Bean 的范围内,并且对于正在计算或初始化重对象的 getter 来说是有效的! 有关详细信息,请参阅此处。
引用自 PrimeFaces forum @ http://forum.primefaces.org/viewtopic.php?f=3&t=29546
最近,我一直痴迷于评估我的应用程序的性能,调整 JPA 查询,用命名查询替换动态 SQL 查询,就在今天早上,我意识到 getter 方法更像是 Java Visual VM 中的一个热点,而不是我的其他代码(或我的大部分代码)。
吸气剂方法:
PageNavigationController.getGmapsAutoComplete()
由 index.xhtml 中的 ui:include 引用
下面,您将看到 PageNavigationController.getGmapsAutoComplete() 是 Java Visual VM 中的热点(性能问题)。如果你再往下看,在屏幕截图上,你会看到 getLazyModel(),PrimeFaces 惰性数据表的 getter 方法,也是一个热点,只有当最终用户在应用程序中做大量“懒惰数据表”类型的东西/操作/任务时。 :)
请参阅下面的(原始)代码。
public Boolean getGmapsAutoComplete() {
switch (page) {
case "/orders/pf_Add.xhtml":
case "/orders/pf_Edit.xhtml":
case "/orders/pf_EditDriverVehicles.xhtml":
gmapsAutoComplete = true;
break;
default:
gmapsAutoComplete = false;
break;
}
return gmapsAutoComplete;
}
由 index.xhtml 中的以下内容引用:
<h:head>
<ui:include src="#{pageNavigationController.gmapsAutoComplete ? '/head_gmapsAutoComplete.xhtml' : (pageNavigationController.gmaps ? '/head_gmaps.xhtml' : '/head_default.xhtml')}"/>
</h:head>
解决方案:由于这是一个“getter”方法,因此在调用方法之前移动代码并将值分配给 gmapsAutoComplete;请参阅下面的代码。
/*
* 2013-04-06 moved switch {...} to updateGmapsAutoComplete()
* because performance = 115ms (hot spot) while
* navigating through web app
*/
public Boolean getGmapsAutoComplete() {
return gmapsAutoComplete;
}
/*
* ALWAYS call this method after "page = ..."
*/
private void updateGmapsAutoComplete() {
switch (page) {
case "/orders/pf_Add.xhtml":
case "/orders/pf_Edit.xhtml":
case "/orders/pf_EditDriverVehicles.xhtml":
gmapsAutoComplete = true;
break;
default:
gmapsAutoComplete = false;
break;
}
}
测试结果:PageNavigationController.getGmapsAutoComplete() 不再是 Java Visual VM 中的热点(甚至不再显示)
分享这个话题,因为许多专家用户建议初级 JSF 开发人员不要在 'getter' 方法中添加代码。:)
我还建议使用诸如 Primefaces 之类的框架而不是库存 JSF,它们在 JSF 团队之前解决了此类问题,例如在 primefaces 中,您可以设置部分提交。否则,BalusC已经很好地解释了它。
评论