为什么JSF多次调用getters

比方说,我指定一个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; } 

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

这种方法有什么替代方法? 有没有办法实现这个没有太多不必要的代码? 有没有办法阻止JSF以这种方式行事?

感谢您的input!

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

这通常会在每个JSF请求 – 响应循环中调用一次或两次,具体取决于组件是input组件还是输出组件(请参阅此处了解 )。 然而,当用于迭代JSF组件(比如<h:dataTable><ui:repeat> )时,这个计数可能会高得多( <h:dataTable> ),或者像rendered属性那样在布尔expression式中出现。 JSF(特别是EL)不会cachingELexpression式的评估结果,因为它可能在每次调用时返回不同的值(例如,当它依赖于当前迭代的数据表行)。

评估一个ELexpression式并调用一个getter方法是一个非常便宜的操作,所以你一般不应该担心这个。 但是,由于某种原因,在getter方法中执行昂贵的数据库/业务逻辑时,故事会改变。 这将每次重新执行!

JSF支持bean中的Getter方法应该这样devise,即它们完全按照Javabeans规范 返回已经准备好的属性。 他们不应该做任何昂贵的数据库/业务逻辑。 为此,应该使用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 (eg <f:viewAction action>). someProperty = loadSomeProperty(); } public void preRender(ComponentSystemEvent event) { // Or in some SystemEvent method (eg <f:event type="preRenderView">). someProperty = loadSomeProperty(); } public void change(ValueChangeEvent event) { // Or in some FacesEvent method (eg <h:inputXxx valueChangeListener>). someProperty = loadSomeProperty(); } public void ajaxListener(AjaxBehaviorEvent event) { // Or in some BehaviorEvent method (eg <f:ajax listener>). someProperty = loadSomeProperty(); } public void actionListener(ActionEvent event) { // Or in some ActionEvent method (eg <h:commandXxx actionListener>). someProperty = loadSomeProperty(); } public String submit() { // Or in POST action method (eg <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的构造函数或初始化块用于作业,因为如果您使用使用代理的beanpipe理框架(如CDI),则可能会多次调用该构造函数或初始化块。

如果你真的没有其他方法,由于一些限制性的devise要求,那么你应该在getter方法中引入延迟加载。 即如果该属性为null ,然后加载并将其分配给该属性,否则返回它。

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

这样,昂贵的数据库/业务逻辑将不会在每一次获取调用时被不必要地执行。

也可以看看:

  • 为什么getter会被渲染的属性多次调用?
  • 在页面加载时调用JSF托pipebean操作
  • 如何以及何时从h:dataTable的数据库加载模型
  • 如何从数据库中填充h:selectOneMenu的选项?
  • 使用p:graphicImage和StreamedContent显示来自数据库的dynamic图像
  • 在JSF页面中定义和重用ELvariables

使用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 AOPcachingJSF bean getter的文章 。

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

这个拦截器用在一个弹簧configuration文件中:

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

希望它会帮助!

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

最近,我一直痴迷于评估我的应用程序的性能,调整JPA查询,用命名查询replacedynamicSQL查询。就在今天上午,我认识到getter方法在Java可视化虚拟机中比起其他我的代码(或我的大部分代码)。

Getter方法:

 PageNavigationController.getGmapsAutoComplete() 

引用ui:include in index.xhtml

下面,您将看到Java可视化虚拟机中的PageNavigationController.getGmapsAutoComplete()是一个HOT SPOT(性能问题)。 如果你仔细观察屏幕截图,你会发现getLazyModel(),PrimeFaces的lazy datatable getter方法也是一个热点,只有当enduser做了很多'懒惰的datatable'types的stuff / operations / tasks在应用程序中。 🙂

Java可视化虚拟机:显示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> 

解决scheme:因为这是一个“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; } } 

testing结果:PageNavigationController.getGmapsAutoComplete()不再是Java Visual VM中的HOT SPOT(甚至不再显示)

分享这个话题,因为许多专家用户build议初级JSF开发人员不要在“getter”方法中添加代码。 🙂

你可能可以使用AOP创build某种forms的caching我们的getter的结果一个可configuration的时间。 这将防止您需要在几十个访问器中复制和粘贴样板代码。

如果您正在使用CDI,则可以使用生产者方法。 它会被调用很多次,但是第一次调用的结果被caching在bean的作用域中,并且对于正在计算或初始化重对象的getter来说是有效的! 在这里看到更多的信息。

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

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

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

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

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

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

这就是我们所说的过早优化。 在极less数情况下,一个分析器告诉你,一个属性的计算是非常昂贵的,而不是一次调用三次就会有明显的性能影响,你可以按照你的描述添加caching。 但是除非你做了一些非常愚蠢的事情,比如把因素分解或者在一个getter中访问数据库,那么你的代码很可能在你从未想过的地方有十几个更糟糕的低效率。