ui:repeat 对每个 ajax 请求计算的值表达式
ui:repeat value expression evaluated on every ajax request
我有一个表单非常简单的页面,它提交 ajax 仅针对同一表单中的组件的请求。在同一个页面中(但在表单之外),还有一个 ui:repeat
迭代从 request scoped managed bean 返回的数组(假设有一个产品类别列表) .这个 bean 没有绑定到表单中的属性,并且除了 ui:repeat
标记的 value
属性之外不能以任何其他方式访问。我不明白为什么 JSF 需要在每个 ajax 回发时重新创建请求范围的 bean,只是 就好像 我要求呈现这个外部 ui:repeat
(它有与表单无关)以及表单中的某些组件。
这是一个错误吗?或者这是一种预期的行为?当然,我可以将 bean 注释为 ViewScoped
,但我看不出将类别存储在视图范围中的原因,因为它们在回发之间是完全静态的。
我发现的另一个 solution/workaround 是仅在非 ajax 请求的情况下呈现 ui:repeat
:
<ul>
<ui:repeat value="#{someRequestScopedBean.categories}" var="category" rendered="#{not facesContext.partialViewContext.ajaxRequest}">
<li>#{category.name}</li>
</ui:repeat>
</ul>
不过不知道会不会出问题,看起来不是很清楚
测试用例
index.xhtml:
<?xml version='1.0' encoding='UTF-8' ?>
<!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://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
<h1>Test page</h1>
<p>Random jokes</p>
<ul>
<ui:repeat value="#{oneLiners.list}" var="oneliner">
<li>#{oneliner}</li>
</ui:repeat>
</ul>
<h:form>
<h:selectOneMenu value="#{backingBean.greeting}" hideNoSelectionOption="true">
<f:selectItem value="#{null}" itemLabel="Select a greeting..." noSelectionOption="true"/>
<f:selectItems value="#{backingBean.greetings}"/>
<f:ajax render="@this btn"/>
</h:selectOneMenu>
<h:commandButton id="btn" value="Say Hello!" disabled="#{empty backingBean.greeting}">
<f:ajax render="otxt"/>
</h:commandButton>
<h:outputText id="otxt" value="#{backingBean.greeting}, Maurizio!" style="display: #{empty backingBean.greeting ? 'none' : 'block'}"/>
</h:form>
</h:body>
</html>
请求作用域 bean:
package testuirepajax;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
/**
*
* @author maurizio
*/
@ManagedBean
@RequestScoped
public class OneLiners {
private String[] list;
public OneLiners() {
System.out.println("testuirepajax.OneLiners.<init>()");
list = new String[] {
"Life is wonderful. Without it we'd all be dead.",
"Daddy, why doesn't this magnet pick up this floppy disk?",
"Daddy, what does FORMATTING DRIVE C mean?",
"Never forget: 2 + 2 = 5 for extremely large values of 2.",
"C:\ is the root of all directories."
};
}
public String[] getList() {
System.out.println("testuirepajax.OneLiners.getList()");
return list;
}
}
表单支持 bean:
package testuirepajax;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
/**
*
* @author maurizio
*/
@ManagedBean
@ViewScoped
public class BackingBean {
private String[] greetings;
private String greeting;
public BackingBean() {
System.out.println("testuirepajax.BackingBean.<init>()");
greetings = new String[] {
"Hello", "Hi", "Good morning", "Good evening", "Good night"
};
}
public String[] getGreetings() {
return greetings;
}
public void setGreeting(String greeting) {
this.greeting = greeting;
}
public String getGreeting() {
return greeting;
}
}
检查容器的输出。使用 Payara 服务器(搭载 Mojarra 2.2.12),我看到如下行:
Informazioni: testuirepajax.OneLiners.<init>()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.<init>()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
从菜单中选择元素或单击 "Say Hello!" 按钮时。
我在 getList()
方法上放置了一个断点,并在回发期间 "unnecessarily" 命中时检查了调用堆栈,以了解谁和为什么:
Daemon Thread [http-nio-8088-exec-5] (Suspended (breakpoint at line 23 in OneLiners))
owns: NioChannel (id=83)
OneLiners.getList() line: 23
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 62
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43
Method.invoke(Object, Object...) line: 497
BeanELResolver.getValue(ELContext, Object, Object) line: 97
DemuxCompositeELResolver._getValue(int, ELResolver[], ELContext, Object, Object) line: 176
DemuxCompositeELResolver.getValue(ELContext, Object, Object) line: 203
AstValue.getValue(EvaluationContext) line: 169
ValueExpressionImpl.getValue(ELContext) line: 184
TagValueExpression.getValue(ELContext) line: 109
UIRepeat.getValue() line: 279
UIRepeat.getDataModel() line: 255
UIRepeat.visitTree(VisitContext, VisitCallback) line: 727
HtmlBody(UIComponent).visitTree(VisitContext, VisitCallback) line: 1700
UIViewRoot(UIComponent).visitTree(VisitContext, VisitCallback) line: 1700
PartialViewContextImpl.processComponents(UIComponent, PhaseId, Collection<String>, FacesContext) line: 403
PartialViewContextImpl.processPartial(PhaseId) line: 266
UIViewRoot.processDecodes(FacesContext) line: 927
ApplyRequestValuesPhase.execute(FacesContext) line: 78
ApplyRequestValuesPhase(Phase).doPhase(FacesContext, Lifecycle, ListIterator<PhaseListener>) line: 101
LifecycleImpl.execute(FacesContext) line: 198
FacesServlet.service(ServletRequest, ServletResponse) line: 658
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 291
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
WsFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 52
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 239
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
StandardWrapperValve.invoke(Request, Response) line: 212
StandardContextValve.invoke(Request, Response) line: 106
FormAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 502
StandardHostValve.invoke(Request, Response) line: 141
ErrorReportValve.invoke(Request, Response) line: 79
AccessLogValve(AbstractAccessLogValve).invoke(Request, Response) line: 616
StandardEngineValve.invoke(Request, Response) line: 88
CoyoteAdapter.service(Request, Response) line: 521
Http11NioProcessor(AbstractHttp11Processor<S>).process(SocketWrapper<S>) line: 1096
Http11NioProtocol$Http11ConnectionHandler(AbstractProtocol$AbstractConnectionHandler<S,P>).process(SocketWrapper<S>, SocketStatus) line: 674
NioEndpoint$SocketProcessor.doRun() line: 1500
NioEndpoint$SocketProcessor.run() line: 1456
ThreadPoolExecutor(ThreadPoolExecutor).runWorker(ThreadPoolExecutor$Worker) line: 1142
ThreadPoolExecutor$Worker.run() line: 617
TaskThread$WrappingRunnable.run() line: 61
TaskThread(Thread).run() line: 745
有趣的是上面的那几行FacesServlet
。 class/method 个名字已经不言自明了。
因此发生在应用请求值阶段,此时部分请求需要处理组件的解码。访问组件树是为了找到由 <f:ajax execute>
(默认为 @this
)中指定的客户端 ID 标识的组件。由于 <ui:repeat>
在感兴趣的组件之前,因此首先对其进行检查。 visitTree()
触发完整迭代,因为感兴趣的客户端 ID 仅在迭代期间可用。
当我将 <ui:repeat>
移动到 <h:form>
下方时,它不再被调用。那时已经找到了所有感兴趣的组件。
不幸的是,这种行为是 "by design"。你的工作很好。最好检查 #{not facesContext.postback}
,因为这也涵盖非 ajax 回发。
我有一个表单非常简单的页面,它提交 ajax 仅针对同一表单中的组件的请求。在同一个页面中(但在表单之外),还有一个 ui:repeat
迭代从 request scoped managed bean 返回的数组(假设有一个产品类别列表) .这个 bean 没有绑定到表单中的属性,并且除了 ui:repeat
标记的 value
属性之外不能以任何其他方式访问。我不明白为什么 JSF 需要在每个 ajax 回发时重新创建请求范围的 bean,只是 就好像 我要求呈现这个外部 ui:repeat
(它有与表单无关)以及表单中的某些组件。
这是一个错误吗?或者这是一种预期的行为?当然,我可以将 bean 注释为 ViewScoped
,但我看不出将类别存储在视图范围中的原因,因为它们在回发之间是完全静态的。
我发现的另一个 solution/workaround 是仅在非 ajax 请求的情况下呈现 ui:repeat
:
<ul>
<ui:repeat value="#{someRequestScopedBean.categories}" var="category" rendered="#{not facesContext.partialViewContext.ajaxRequest}">
<li>#{category.name}</li>
</ui:repeat>
</ul>
不过不知道会不会出问题,看起来不是很清楚
测试用例
index.xhtml:
<?xml version='1.0' encoding='UTF-8' ?>
<!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://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<h:head>
<title>Facelet Title</title>
</h:head>
<h:body>
<h1>Test page</h1>
<p>Random jokes</p>
<ul>
<ui:repeat value="#{oneLiners.list}" var="oneliner">
<li>#{oneliner}</li>
</ui:repeat>
</ul>
<h:form>
<h:selectOneMenu value="#{backingBean.greeting}" hideNoSelectionOption="true">
<f:selectItem value="#{null}" itemLabel="Select a greeting..." noSelectionOption="true"/>
<f:selectItems value="#{backingBean.greetings}"/>
<f:ajax render="@this btn"/>
</h:selectOneMenu>
<h:commandButton id="btn" value="Say Hello!" disabled="#{empty backingBean.greeting}">
<f:ajax render="otxt"/>
</h:commandButton>
<h:outputText id="otxt" value="#{backingBean.greeting}, Maurizio!" style="display: #{empty backingBean.greeting ? 'none' : 'block'}"/>
</h:form>
</h:body>
</html>
请求作用域 bean:
package testuirepajax;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
/**
*
* @author maurizio
*/
@ManagedBean
@RequestScoped
public class OneLiners {
private String[] list;
public OneLiners() {
System.out.println("testuirepajax.OneLiners.<init>()");
list = new String[] {
"Life is wonderful. Without it we'd all be dead.",
"Daddy, why doesn't this magnet pick up this floppy disk?",
"Daddy, what does FORMATTING DRIVE C mean?",
"Never forget: 2 + 2 = 5 for extremely large values of 2.",
"C:\ is the root of all directories."
};
}
public String[] getList() {
System.out.println("testuirepajax.OneLiners.getList()");
return list;
}
}
表单支持 bean:
package testuirepajax;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
/**
*
* @author maurizio
*/
@ManagedBean
@ViewScoped
public class BackingBean {
private String[] greetings;
private String greeting;
public BackingBean() {
System.out.println("testuirepajax.BackingBean.<init>()");
greetings = new String[] {
"Hello", "Hi", "Good morning", "Good evening", "Good night"
};
}
public String[] getGreetings() {
return greetings;
}
public void setGreeting(String greeting) {
this.greeting = greeting;
}
public String getGreeting() {
return greeting;
}
}
检查容器的输出。使用 Payara 服务器(搭载 Mojarra 2.2.12),我看到如下行:
Informazioni: testuirepajax.OneLiners.<init>()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.<init>()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
Informazioni: testuirepajax.OneLiners.getList()
从菜单中选择元素或单击 "Say Hello!" 按钮时。
我在 getList()
方法上放置了一个断点,并在回发期间 "unnecessarily" 命中时检查了调用堆栈,以了解谁和为什么:
Daemon Thread [http-nio-8088-exec-5] (Suspended (breakpoint at line 23 in OneLiners))
owns: NioChannel (id=83)
OneLiners.getList() line: 23
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 62
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43
Method.invoke(Object, Object...) line: 497
BeanELResolver.getValue(ELContext, Object, Object) line: 97
DemuxCompositeELResolver._getValue(int, ELResolver[], ELContext, Object, Object) line: 176
DemuxCompositeELResolver.getValue(ELContext, Object, Object) line: 203
AstValue.getValue(EvaluationContext) line: 169
ValueExpressionImpl.getValue(ELContext) line: 184
TagValueExpression.getValue(ELContext) line: 109
UIRepeat.getValue() line: 279
UIRepeat.getDataModel() line: 255
UIRepeat.visitTree(VisitContext, VisitCallback) line: 727
HtmlBody(UIComponent).visitTree(VisitContext, VisitCallback) line: 1700
UIViewRoot(UIComponent).visitTree(VisitContext, VisitCallback) line: 1700
PartialViewContextImpl.processComponents(UIComponent, PhaseId, Collection<String>, FacesContext) line: 403
PartialViewContextImpl.processPartial(PhaseId) line: 266
UIViewRoot.processDecodes(FacesContext) line: 927
ApplyRequestValuesPhase.execute(FacesContext) line: 78
ApplyRequestValuesPhase(Phase).doPhase(FacesContext, Lifecycle, ListIterator<PhaseListener>) line: 101
LifecycleImpl.execute(FacesContext) line: 198
FacesServlet.service(ServletRequest, ServletResponse) line: 658
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 291
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
WsFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 52
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 239
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206
StandardWrapperValve.invoke(Request, Response) line: 212
StandardContextValve.invoke(Request, Response) line: 106
FormAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 502
StandardHostValve.invoke(Request, Response) line: 141
ErrorReportValve.invoke(Request, Response) line: 79
AccessLogValve(AbstractAccessLogValve).invoke(Request, Response) line: 616
StandardEngineValve.invoke(Request, Response) line: 88
CoyoteAdapter.service(Request, Response) line: 521
Http11NioProcessor(AbstractHttp11Processor<S>).process(SocketWrapper<S>) line: 1096
Http11NioProtocol$Http11ConnectionHandler(AbstractProtocol$AbstractConnectionHandler<S,P>).process(SocketWrapper<S>, SocketStatus) line: 674
NioEndpoint$SocketProcessor.doRun() line: 1500
NioEndpoint$SocketProcessor.run() line: 1456
ThreadPoolExecutor(ThreadPoolExecutor).runWorker(ThreadPoolExecutor$Worker) line: 1142
ThreadPoolExecutor$Worker.run() line: 617
TaskThread$WrappingRunnable.run() line: 61
TaskThread(Thread).run() line: 745
有趣的是上面的那几行FacesServlet
。 class/method 个名字已经不言自明了。
因此发生在应用请求值阶段,此时部分请求需要处理组件的解码。访问组件树是为了找到由 <f:ajax execute>
(默认为 @this
)中指定的客户端 ID 标识的组件。由于 <ui:repeat>
在感兴趣的组件之前,因此首先对其进行检查。 visitTree()
触发完整迭代,因为感兴趣的客户端 ID 仅在迭代期间可用。
当我将 <ui:repeat>
移动到 <h:form>
下方时,它不再被调用。那时已经找到了所有感兴趣的组件。
不幸的是,这种行为是 "by design"。你的工作很好。最好检查 #{not facesContext.postback}
,因为这也涵盖非 ajax 回发。