我有一个页面,其中包含一个非常简单的表单,它提交仅针对同一表单中的组件的ajax请求。在同一页面中(但在表单之外),还有一个ui:repeat
,它迭代从请求范围的托管bean返回的数组(假设产品类别列表)。此bean没有在表单中绑定的属性,除了value
标记的ui:repeat
属性之外,不会以任何其他方式访问该bean。我不明白为什么JSF需要在每个ajax回发上重新创建请求范围的bean,只是好像我要求渲染这个外部ui:repeat
(与表单无关)以及表单中的一些组件。
这是一个错误吗?或者这是一种预期的行为?当然我可以将bean注释为ViewScoped
但我没有看到将类别存储在视图范围中的原因,因为它们在回发之间是完全静态的。
我找到的另一个解决方案/解决方法是仅在非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 Server(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!”时按钮。
答案 0 :(得分:2)
我在getList()
方法上放置了一个断点,并在回发期间“不必要地”命中时检查了调用堆栈,以便了解谁和为什么:
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
以上的行。这个类/方法名称已经说明了一切。
因此,当部分请求需要处理组件的解码时,它发生在应用请求值阶段期间。访问组件树是为了查找由<f:ajax execute>
中指定的客户端ID标识的组件(默认为@this
)。由于<ui:repeat>
在感兴趣的组件之前已经存在,因此首先进行检查。 visitTree()
触发完整迭代,因为感兴趣的客户端ID仅在迭代期间可用。
当我将<ui:repeat>
移到<h:form>
以下时,不会再调用它。此时已经找到了所有感兴趣的组件。
遗憾的是,这种行为是“按设计”的。你的工作是一个很好的。更好的方法是检查#{not facesContext.postback}
,因为这也包括非ajax回发。