ui:在每个ajax请求上评估重复值表达式

时间:2016-02-22 17:21:50

标签: ajax jsf uirepeat

我有一个页面,其中包含一个非常简单的表单,它提交仅针对同一表单中的组件的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!”时按钮。

1 个答案:

答案 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回发。