我有一个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
(出现在导致已计算方法调用的每个堆栈跟踪中)提供了一个映射来完成这种缓存。但是,这似乎并不像我预期的那样有效,并且总是重新评估表达式......
答案 0 :(得分:2)
我知道这个有点老了,但我想补充一点,这个问题已经通过MyFaces实现解决了。它记录在他们的Wiki中:https://cwiki.apache.org/confluence/display/MYFACES/Cache+EL+Expressions
答案 1 :(得分:1)
1。)有没有办法加快方法表达式的解析。也许隐藏的“启用缓存”标志或其他东西。
没有人想到。
2。)似乎大多数表达式不是在渲染响应阶段内进行评估,实际需要它们,但在其他阶段。在渲染阶段之外的任何其他阶段,似乎没有必要解决例如
styleClass
。我可以阻止这个吗?
据我所知,这应该不会发生。在呈现响应之前唯一可以/应该解决的是rendered
,required
,disabled
,readonly
和value
。
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}:"/>
<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结合时有问题! * 强>