264

假设我指定了一个 outputText 组件,如下所示:

<h:outputText value="#{ManagedBean.someProperty}"/>

如果我在someProperty调用 getter for 并加载页面时打印一条日志消息,那么很容易注意到每个请求都多次调用 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 以这种方式行事?

感谢您的输入!

4

9 回答 9

352

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

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

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

JSF 支持 bean 中的 Getter 方法应该被设计成只返回已经准备好的属性,仅此而已,完全符合Javabeans 规范。他们根本不应该做任何昂贵的数据库/业务逻辑。为此,应使用 bean 的@PostConstruct和/或(动作)侦听器方法。它们仅在基于请求的 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;
    }

这样,昂贵的数据库/业务逻辑就不会在每个 getter 调用上不必要地执行。

也可以看看:

于 2010-01-18T23:51:32.583 回答
18

使用 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>
于 2010-10-31T01:09:08.513 回答
9

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

希望它会有所帮助!

于 2011-01-05T14:02:39.337 回答
6

原帖于 PrimeFaces 论坛@http: //forum.primefaces.org/viewtopic.php? f=3&t=29546

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

吸气剂方法:

PageNavigationController.getGmapsAutoComplete()

被 ui:include 引用在 index.xhtml 中

下面,您将看到 PageNavigationController.getGmapsAutoComplete() 是 Java Visual VM 中的一个热点(性能问题)。如果你往下看,在屏幕截图上,你会看到 getLazyModel(),PrimeFaces 惰性数据表 getter 方法,也是一个热点,只有当最终用户执行大量“惰性数据表”类型的东西/操作/任务时在应用程序中。:)

Java Visual VM:显示热点

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

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

于 2013-04-14T13:32:03.427 回答
4

如果您使用 CDI,则可以使用 Producers 方法。它会被多次调用,但第一次调用的结果被缓存在 bean 的范围内,对于计算或初始化重对象的 getter 来说是有效的!请参阅此处,了解更多信息。

于 2012-06-11T11:50:13.007 回答
3

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

于 2010-01-18T23:45:53.533 回答
-1

如果 someProperty 的值计算起来很昂贵,这可能是一个问题。

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

于 2010-01-19T00:39:14.907 回答
-1

我还建议使用 Primefaces 之类的框架而不是股票 JSF,他们在 JSF 团队 e 之前解决了这些问题。g 在 primefaces 中你可以设置部分提交。否则 BalusC 已经很好地解释了它。

于 2019-06-26T15:24:30.873 回答
-2

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

安全检查可能很复杂,例如。LDAP查询等。所以你必须避免这种情况

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

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

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

于 2012-01-06T13:12:50.683 回答