解析EL值表达式的性能

时间:2010-08-05 10:07:20

标签: performance jsf jsf-2 el

我有一个JSF2应用程序,它呈现一个包含复杂内容的大表。不幸的是,每个请求最多需要6秒钟来处理。在阶段监听器中使用简单的调试输出,我可以看到性能损失在处理组件树的所有阶段上均匀分布。所以我启动了一个分析器来查看详细情况,发现在一个简单的请求中评估了超过300.000个ValueExpressions。

他们解决了没有任何逻辑的非常简单的getter,所以问题不是执行这些表达式背后的代码,而是解析表达式字符串并调用getter方法。这导致了一些问题:

1。)有没有办法加快方法表达式的解析。也许隐藏的“启用缓存”标志或其他东西。

2。)似乎大多数表达式不是在渲染响应阶段内进行评估,实际需要它们,但在其他阶段。在渲染阶段之外的任何其他阶段,似乎没有必要解决例如styleClass。我可以阻止这个吗?

3。)当然,最小化facelets页面中EL表达式的数量应该有助于获得更多性能,但似乎我不能真正做到这一点:许多属性(如上面提到的styleClass示例)是实际上依赖于表行,但只能在列上设置。因此,有10列,每个表达式都经常被评估。我已经看到了使用表的rowClasses属性来有条件地设置行的样式的示例,但由于表是可排序的,如果不滚动我自己的排序机制,这将无法工作。有没有更好的方法来实现这个?

4.。)一个更简单的问题:是否有办法在组件树中缓存变量(就像ui:repeat提供对列表内容的访问权限并解析表达式只获取列表一次,但是仅用于一个变量)?

非常感谢您的所有答案和提示!

修改

经过进一步调查,我发现对于每个rendered=#{someExpression},表达式在渲染响应阶段每行评估6次。我知道JSF可能不止一次地调用我的getter,但我认为这是因为它们可以在每个阶段调用。在渲染过程中,这些值不应该改变,所以我猜它们可以被缓存。

单步执行调试器中的代码,它看起来像javax.faces.component.ComponentStateHelper(出现在导致已计算方法调用的每个堆栈跟踪中)提供了一个映射来完成这种缓存。但是,这似乎并不像我预期的那样有效,并且总是重新评估表达式......

5 个答案:

答案 0 :(得分:2)

我知道这个有点老了,但我想补充一点,这个问题已经通过MyFaces实现解决了。它记录在他们的Wiki中:https://cwiki.apache.org/confluence/display/MYFACES/Cache+EL+Expressions

答案 1 :(得分:1)

  

1。)有没有办法加快方法表达式的解析。也许隐藏的“启用缓存”标志或其他东西。

没有人想到。

  

2。)似乎大多数表达式不是在渲染响应阶段内进行评估,实际需要它们,但在其他阶段。在渲染阶段之外的任何其他阶段,似乎没有必要解决例如styleClass。我可以阻止这个吗?

据我所知,这应该不会发生。在呈现响应之前唯一可以/应该解决的是renderedrequireddisabledreadonlyvalue

  

3。)当然,最小化我的facelets页面中EL表达式的数量应该有助于获得更多性能,但似乎我不能真正做到这一点:许多属性(如上面提到的styleClass示例)实际上依赖于表行,但只能在列上设置。因此,有10列,每个表达式都经常被评估。我已经看到了一些示例,其中表的rowClasses属性用于有条件地设置行的样式,但由于表是可排序的,如果没有滚动我自己的排序机制,这将无法工作。有没有更好的方法来实现这个?

您可以将样式工作移交给智能/ JS / CSS组合。

  

4。)一个更简单的问题:是否有一种方法可以在组件树中缓存变量(就像ui:repeat提供对列表内容的访问并解析表达式只获取列表一次,但只是为了一个变量)?

使用JSTL <c:set>。我不确定这会怎样影响,但你基本上只是将问题转移到其他地方。 #{variableName}仍然需要在任何范围内定位它。您还可以考虑在访问变量时明确命名范围。例如。 #{sessionScope.beanname}应该跳过不必要的页面扫描和请求范围。

答案 2 :(得分:1)

如果您在glassfish上使用mojarra参考实现,您可以尝试按照blog post by Ed Burns中提到的每晚构建。 oracle adf开发人员对el表达式评估做出了一些性能改进。

不确定这是否相关,但您也可以尝试通过将init参数javax.faces.PARTIAL_STATE_SAVING设置为false来禁用部分状态保存。

答案 3 :(得分:1)

使用复合组件时,我在托管bean上遇到重复的getter调用,特别是渲染的getter被称为百万次。我不知道您是否也使用它们,但我希望您对我的缓存解决方案有所了解。

当我在问题Binding a managed bean instance to composite component中跟踪BalusC的支持组件提示时,我成功地进行了一些缓存行为。

复合组件:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:composite="http://java.sun.com/jsf/composite"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets">

<!-- INTERFACE -->
<composite:interface componentType="fieldComponentType">
    <composite:attribute name="id" type="java.lang.String" required="true" />
    <composite:attribute name="label" type="java.lang.String" required="true"/>
    <composite:attribute name="toBeRendered" type="java.lang.Boolean" required="true" />
    <composite:attribute name="currentValue" required="true" />
</composite:interface>

<!-- IMPLEMENTATION -->
<composite:implementation>
    <h:panelGrid rendered="#{cc._toBeRendered}" columns="3">
        <h:outputText value="#{cc._label}:"/>&nbsp;
        <h:inputText id="#{cc.attrs.id}" rendered="#{cc._toBeRendered}"
            value="#{cc.attrs.currentValue}" /> 
    </h:panelGrid>
</composite:implementation>
</html>    

在一个阶段内提供缓存的支持组件:

package cz.kamosh;

import javax.faces.component.FacesComponent;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;

@FacesComponent(value = "fieldComponentType")
public final class FieldComponentType extends UIComponentBase implements NamingContainer {

    static class Setting {
        String label;
        Boolean toBeRendered;

        @Override
        public String toString() {
            return "Setting [label=" + label + ", toBeRendered=" + toBeRendered
                    + "]";
        }       
    }

    int lastPhaseId = -1;

    Setting currentSetting = null;

    public FieldComponentType() {
        System.out.println("Constructor FieldComponentType");
    }   

    @Override
    public String getFamily() {
        return "javax.faces.NamingContainer";
    }

    // Must be named with prefix _, otherwise infinite loop occurs
    public String get_label() {
        Setting setting = getSetting();
        if (setting.label == null) {
            setting.label = (String) getAttributes().get("label");
        }
        return setting.label;
    }

    // Must be named with prefix _, otherwise infinite loop occurs
    public boolean is_toBeRendered() {
        Setting setting = getSetting();
        if (setting.toBeRendered == null) {
            setting.toBeRendered = (Boolean) getAttributes().get("toBeRendered");
        }
        return setting.toBeRendered;
    }

    private Setting getSetting() {
        int phaseId = FacesContext.getCurrentInstance().getCurrentPhaseId()
                .getOrdinal();

        if (currentSetting == null || phaseId > lastPhaseId) {
            currentSetting = new Setting();
            lastPhaseId = phaseId;
        }
        return currentSetting;
    }

}

测试页面:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:util="http://java.sun.com/jsf/composite/components">

<h:head>
    <title>Testing page</title>
</h:head>
<h:body>

    <h:form>
        <h:panelGrid>           
            <util:fieldComponent id="id3"
                label="#{testingBean.label}"
                toBeRendered="#{testingBean.toBeRendered}"
                currentValue="#{testingBean.myValue}" />

        </h:panelGrid>
        <h:commandButton value="Do something" actionListener="#{testingBean.doSomething}" />
    </h:form>

</h:body>
</html>

管理bean:

package cz.kamosh;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

@ViewScoped
@ManagedBean(name="testingBean")
public class TestingBean {

    private String myValue;

    public String getMyValue() {
        System.out.printf("getMyValue: %1$s\n", myValue);
        return myValue;
    }

    public void setMyValue(String myValue) {
        System.out.printf("setMyValue: %1$s\n", myValue);
        this.myValue = myValue;
    }

    public void doSomething() {
        System.out.printf("Do something, myValue: %1$s\n", this.myValue);
    }

    public String getLabel() {
        System.out.printf("getLabel\n");
        return "My value lbl";
    }

    public boolean isToBeRendered() {
        System.out.printf("isToBeRendered\n");
        return true;
    }

}

在使用jvisualvm 26进行分析后,com.sun.faces.facelets.el.TagValueExpression.getValue的调用从一个完整请求“运行”所花费的总时间的26%减少到16%(与不使用的复合组件相比)使用componentType =“fieldComponentType” - 此答案中未包含的来源。)

但是,无论如何,JSF2框架本身的开销花费了大约80%的时间,而我的代码花费了20%,即使我调用了一些数据库提取(这是我的生产代码中的经验)。我认为这个开销很大: - (

@FRoothowe你提到“在渲染过程中,这些值不应该改变,所以我猜它们可以被缓存。”从这个角度来看,我可以在每个阶段内提供缓存标记表达式值,我是对的吗?

答案 4 :(得分:1)

经过几个小时的调试后,我决定改进ComponentStateHelper,这是Mojarra JSF2实现的一部分。 我拿了最后一个版本2.1.4 https://maven.java.net/content/repositories/releases/org/glassfish/javax.faces/2.1.4/javax.faces-2.1.4-sources.jar

主要思想是保留每个阶段评估的EL表达式的结果。我仍然认为EL表达式的结果必须在一个阶段内相同。

类javax.faces.component.ComponentStateHelper:

中的更改
class ComponentStateHelper implements StateHelper , TransientStateHelper {    

    ...

    // Own cache for method public Object eval(Serializable key, Object defaultValue) {
    int lastPhaseId = -1; // Last cached phase
    private Map<Serializable, Object> evalCache;

    ...
    public ComponentStateHelper(UIComponent component) {

        ... 
        // Instantiate own cache
        this.evalCache = new HashMap<Serializable, Object>();
    }

    ...

    /**
     * @see StateHelper#eval(java.io.Serializable, Object)
     */
    public Object eval(Serializable key, Object defaultValue) {
        Object retVal = get(key);        
        if (retVal == null) { 
            // Value evaluated and returned within one phase should be hopefully still same
            int currentPhaseId = FacesContext.getCurrentInstance().getCurrentPhaseId().getOrdinal();
            if(lastPhaseId < currentPhaseId) {
                // Probably stale cache, so clear it to get fresh results
                // in current phase
                evalCache.clear();
                lastPhaseId = currentPhaseId;
            }
            retVal = evalCache.get(key);     

            if(retVal == null) { 
                ValueExpression ve = component.getValueExpression(key.toString());
                if (ve != null) {
                    retVal = ve.getValue(component.getFacesContext().getELContext());
                }
            }
            // Remember returned value in own cache
            evalCache.put(key, retVal);         
        } 
        return ((retVal != null) ? retVal : defaultValue);
    }

    ...    
}

这个增强似乎是功能性的,并且对托管bean的调用次数大幅减少,尤其是呈现的 getter,它们被同一个组件多次调用。

我可能没有看到这种增强可能造成的灾难性后果。但如果这是可行的,我想知道为什么JSF人员没有使用这种类型的缓存。

* 编辑:此解决方案无法使用,因为它与DataModel结合时有问题! *