假设我像这样指定了一个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以这种方式行为?

谢谢你的建议!


当前回答

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

其他回答

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

这仍然是JSF中的一个大问题。例如,如果你有一个isPermittedToBlaBla方法用于安全检查,并且在你的视图中你已经呈现了="#{bean。isPermittedToBlaBla}则该方法将被调用多次。

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

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

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

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

如果someProperty的值为 昂贵的计算,这可以 可能是个问题。

这就是我们所说的过早优化。在极少数情况下,如果分析器告诉您某个属性的计算非常昂贵,以至于调用它三次(而不是一次)会产生显著的性能影响,那么您就可以按照您的描述添加缓存。但是,除非您做了一些非常愚蠢的事情,如分解质数或在getter中访问数据库,否则您的代码很可能在您从未想过的地方存在十几个更糟糕的低效率。

我还建议使用这样的框架作为Primefaces,而不是库存JSF,它们可以在JSF团队之前解决这些问题,例如在Primefaces中你可以设置部分提交。除此之外,BalusC解释得很好。

我写过一篇关于如何用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>

希望对大家有所帮助!