为什么JSF多次调用getter

时间:2010-01-18 23:43:40

标签: performance jsf el getter

假设我像这样指定一个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的值计算成本很高,则可能存在问题。

我用Google搜索了一下,认为这是一个已知问题。一个解决方法是包括一个检查,看看它是否已经计算过:

private String someProperty;

public String getSomeProperty() {
    if (this.someProperty == null) {
        this.someProperty = this.calculatePropertyValue();
    }
    return this.someProperty;
}

这个问题的主要问题是你得到大量的样板代码,更不用说你可能不需要的私有变量了。

这种方法有哪些替代方案?没有那么多不必要的代码,有没有办法实现这一目标?有没有办法阻止JSF以这种方式行事?

感谢您的投入!

9 个答案:

答案 0 :(得分:328)

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

每个JSF请求 - 响应周期通常会调用一次或两次,具体取决于组件是输入组件还是输出组件(learn it here)。但是,当用于迭代JSF组件(例如<h:dataTable><ui:repeat>)时,或者在rendered属性之类的布尔表达式中,此计数可以更高(更高)。 JSF(特别是EL)根本不会缓存EL表达式的计算结果,因为可能在每次调用时返回不同的值(例如,当它依赖于当前迭代的数据表行时)。

评估EL表达式并调用getter方法是一种非常便宜的操作,因此您通常不应该担心这一点。但是,当您出于某种原因在getter方法中执行昂贵的DB /业务逻辑时,故事会发生变化。每次都会重新执行!

JSF支持bean中的Getter方法的设计应该只是返回已经准备好的属性,而不是Javabeans specification。他们根本不应该做任何昂贵的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方法中引入延迟加载。即如果属性为null,则加载并将其分配给属性,否则返回它。

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

        return someProperty;
    }

这样,昂贵的DB /业务逻辑就不会在每次getter调用时不必要地执行。

另见:

答案 1 :(得分:16)

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

答案 2 :(得分:8)

我写了一篇关于如何使用Spring AOP缓存JSF bean getter的article

我创建了一个简单的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;
}

}

此拦截器用于弹簧配置文件:

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

希望它会有所帮助!

答案 3 :(得分:6)

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

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

Getter方法:

PageNavigationController.getGmapsAutoComplete()

由ui引用:包含在index.xhtml

下面,您将看到PageNavigationController.getGmapsAutoComplete()是Java Visual VM中的HOT SPOT(性能问题)。如果你向下看,在屏幕截图上,你会看到getLazyModel(),PrimeFaces懒惰的数据表getter方法,也是一个热点,只有当enduser正在做很多'lazy datatable'类型的东西/操作/任务时在应用程序中。 :)

Java Visual VM: showing HOT SPOT

请参阅下面的(原始)代码。

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'方法中添加代码。 :)

答案 4 :(得分:3)

您可以使用AOP创建某种Aspect,它可以在可配置的时间内缓存我们的getter的结果。这样可以防止您需要在许多访问器中复制和粘贴样板代码。

答案 5 :(得分:3)

如果您使用的是CDI,则可以使用Producers方法。 它将被多次调用,但第一次调用的结果缓存在bean的范围内,对于计算或初始化重物的getter是有效的! 有关详细信息,请参阅here

答案 6 :(得分:0)

它仍然是JSF中的大问题。例如,如果您有一个方法isPermittedToBlaBla进行安全检查,并且在您的视图中有rendered="#{bean.isPermittedToBlaBla},那么该方法将被多次调用。

安全检查可能很复杂,例如。 LDAP查询等因此您必须使用

来避免这种情况
Boolean isAllowed = null ... if(isAllowed==null){...} return isAllowed?

并且您必须在会话bean中确保每个请求。

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

答案 7 :(得分:-1)

  

如果someProperty的值是   计算成本高,这可以   可能是个问题。

这就是我们所说的过早优化。在极少数情况下,分析器会告诉您属性的计算非常昂贵,因此调用它三次而不是一次会对性能产生重大影响,您可以按照描述添加缓存。但是,除非你做一些非常愚蠢的事情,比如分析素数或在getter中访问数据库,你的代码很可能在你从未想过的地方有十几个效率低下的效率。

答案 8 :(得分:-1)

我还建议使用诸如Primefaces之类的框架,而不是股票JSF,它们会在JSF团队e之前解决此类问题。 g您可以将其设置为部分提交。否则,BalusC解释得很好。