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


这是由延迟表达式#{}的性质引起的(注意,当使用Facelets而不是JSP时,“遗留”标准表达式${}的行为完全相同)。延迟的表达式不会立即求值,而是作为ValueExpression对象创建,并且在代码每次调用ValueExpression#getValue()时执行表达式后面的getter方法。

This will normally be invoked one or two times per JSF request-response cycle, depending on whether the component is an input or output component (learn it here). However, this count can get up (much) higher when used in iterating JSF components (such as <h:dataTable> and <ui:repeat>), or here and there in a boolean expression like the rendered attribute. JSF (specifically, EL) won't cache the evaluated result of the EL expression at all as it may return different values on each call (for example, when it's dependent on the currently iterated datatable row).

求值EL表达式并调用getter方法是一种非常廉价的操作,因此通常不需要担心这一点。但是,由于某种原因,当您在getter方法中执行昂贵的DB/业务逻辑时,情况就会发生变化。每次都会重新执行!

JSF支持bean中的Getter方法应该按照Javabeans规范的要求设计成只返回已经准备好的属性而不返回其他任何东西。他们根本不应该做任何昂贵的DB/业务逻辑。为此,应该使用bean的@PostConstruct和/或(action)侦听器方法。它们只在基于请求的JSF生命周期的某个点执行一次,这正是您想要的。

下面是所有预设/加载属性的正确方法的总结。

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的构造函数或初始化块,因为如果您正在使用使用代理的bean管理框架(如CDI),那么它可能会被多次调用。

如果由于某些限制性的设计需求,确实没有其他方法,那么您应该在getter方法中引入延迟加载。例如,如果属性为空,则加载并将其分配给属性,否则返回它。

    public SomeObject getSomeProperty() {
        // If there are really no other ways, introduce lazy loading.
        if (someProperty == null) {
            someProperty = loadSomeProperty();
        }

        return someProperty;
    }

这样就不会在每个getter调用上都执行昂贵的DB/业务逻辑。

参见:

为什么getter被渲染的属性调用了这么多次? 在页面加载时调用JSF托管bean操作 我应该如何以及何时从数据库h:dataTable加载模型 如何填充选项h:selectOneMenu从数据库? 使用p:graphicImage和StreamedContent显示数据库中的动态图像 在JSF页面中定义和重用EL变量 在服务器请求之后测量JSF视图的呈现时间


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

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

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

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

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


如果您正在使用CDI,您可以使用生产者方法。 它将被多次调用,但是第一次调用的结果缓存在bean的作用域中,对于正在计算或初始化沉重对象的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”方法中添加代码。:)


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