假设我像这样指定了一个outputText组件:

<h:outputText value="#{ManagedBean.someProperty}"/>

如果我打印一条日志消息,当someProperty的getter被调用并加载页面时,很容易注意到getter在每个请求中被调用了不止一次(在我的情况下发生了两次或三次):

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的结果。这将避免您需要在数十个访问器中复制并粘贴样板代码。

其他回答

最初发布于PrimeFaces论坛@ http://forum.primefaces.org/viewtopic.php?f=3&t=29546

最近,我一直痴迷于评估我的应用程序的性能,调优JPA查询,用命名查询替换动态SQL查询,就在今天早上,我意识到getter方法在Java Visual VM中比我的其余代码(或大部分代码)更像一个热点。

Getter方法:

PageNavigationController.getGmapsAutoComplete()

由ui:include在index.xhtml中引用

下面,你会看到pagenavigationcontroller . getmapsautocomplete()在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”方法中添加代码。:)

您可以使用AOP来创建某种Aspect,在可配置的时间内缓存getter的结果。这将避免您需要在数十个访问器中复制并粘贴样板代码。

我写过一篇关于如何用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方法用于安全检查,并且在你的视图中你已经呈现了="#{bean。isPermittedToBlaBla}则该方法将被调用多次。

安全检查可能会很复杂。LDAP查询等。所以你必须避免

Boolean isAllowed = null ... if(isAllowed==null){...} return isAllowed?

您必须在会话bean中确保每个请求都有此功能。

我认为JSF必须在这里实现一些扩展来避免多次调用(例如annotation @Phase(RENDER_RESPONSE)只在RENDER_RESPONSE阶段之后调用此方法一次…)

使用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>