假设我指定了一个 outputText 组件,如下所示:
<h:outputText value="#{ManagedBean.someProperty}"/>
如果我在someProperty调用 getter for 并加载页面时打印一条日志消息,那么很容易注意到每个请求都多次调用 getter(在我的情况下发生了两次或三次):
someProperty
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 以这种方式行事?
感谢您的输入!
这是由延迟表达式的性质引起的#{}(请注意,${}当使用 Facelets 而不是 JSP 时,“遗留”标准表达式的行为完全相同)。延迟表达式不会 立即 计算,而是创建为ValueExpression对象,并且每次代码调用时都会执行表达式后面的 getter 方法ValueExpression#getValue()。
#{}
${}
ValueExpression
ValueExpression#getValue()
这通常会在每个 JSF 请求- 响应周期中调用一到两次,具体取决于组件是输入组件还是输出组件(在此处了解)。但是,当用于迭代 JSF 组件(例如<h:dataTable>and <ui:repeat>)时,或者在诸如rendered属性之类的布尔表达式中到处使用时,这个计数可能会上升(很多)。JSF(特别是 EL)根本不会缓存 EL 表达式的计算结果,因为它 可能 在每次调用时返回不同的值(例如,当它依赖于当前迭代的数据表行时)。
<h:dataTable>
<ui:repeat>
rendered
评估 EL 表达式并调用 getter 方法是一项非常便宜的操作,因此您通常根本不必担心这一点。但是,由于某种原因,当您在 getter 方法中执行昂贵的数据库/业务逻辑时,情况就会发生变化。每次都会重新执行!
JSF 支持 bean 中的 Getter 方法应该被设计成只 返回 已经准备好的属性,仅此而已,完全符合Javabeans 规范。他们根本不应该做任何昂贵的数据库/业务逻辑。为此,应使用 bean 的@PostConstruct和/或(动作)侦听器方法。它们仅在基于请求的 JSF 生命周期的某个时间点执行 一次 ,而这正是您想要的。
@PostConstruct
这是预设/加载属性的所有不同 正确 方法的摘要。
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,则加载并将其分配给该属性,否则返回它。
null
public SomeObject getSomeProperty() { // If there are really no other ways, introduce lazy loading. if (someProperty == null) { someProperty = loadSomeProperty(); } return someProperty; }
这样,昂贵的数据库/业务逻辑就不会在每个 getter 调用上不必要地执行。