commandButton单击p:dataTable导致在应用程序调用后重新生成@ViewScoped bean

Mat*_*yas 5 java jsf glassfish cdi view-scope

风景:

<h:form ...
  <p:dataTable value="#{myBean.list}" var="data" ...
     <p:column ...
        <h:commandButton action="#{controller.method(data.id)}" />
     </p:column>
  </p:dataTable>
</h:form>
Run Code Online (Sandbox Code Playgroud)

控制器:

@ApplicationScoped
public class Controller {
   public String method(final Long dataId) {
        /* Do business */
        return URL_WITH_REDIRECT;
   }
}
Run Code Online (Sandbox Code Playgroud)

制片人

(使用此处@ViewScoped描述的CDI注释)

@ApplicationScoped
public class Producer {
   @Named @ViewScoped @Producer
   public MyBean getMyBean() {
        final MyBean bean = new MyBean();
        bean.list = new ArrayList<Data>(); // where Data has a Long id field
        /* Do business and populate list */
        return bean;
   }
}
Run Code Online (Sandbox Code Playgroud)

问题及其情况

  1. GET 这页纸
    1. 豆子是生产出来的
    2. 视图已呈现
    3. 响应发送到浏览器
  2. 单击按钮
    1. 数据被POST编辑到服务器
    2. 阶段 1-4执行没有任何问题,并且@ViewScoped按预期使用bean
    3. 阶段5:controller.method调用data.id并访问1.1生成的bean
    4. 方法返回重定向 String
    5. !生产者再次被召唤!! - 我们仍处于APPLICATION_INVOCATION阶段,但在实际的方法调用之后
  3. 浏览器接收重定向
  4. GET 下一页 ...

有效的半"驴"解决方案:

简而言之:单击时,将id复制到数据表外部,然后单击提交按钮.

h:commandButton表格栏的内部添加了:

onclick="$('input[id*=selectedDataId]').val('#{data.id}'); $('button[id*=callMethod]').trigger('click');"
Run Code Online (Sandbox Code Playgroud)

在桌子外面:

<h:inputHidden id="{selectedDataId}"binding="#{selectedDataId}"/>
<p:commandButton type="submit"
                 id="callMethod"
                 label="Hidden button"
                 action="#{controller.method(selectedDataId.value)}"/>
Run Code Online (Sandbox Code Playgroud)

最后它可以工作,但我无法弄清楚导致第一个和基本方法重新初始化视图范围bean的原因.查看堆栈跟踪(见下文),似乎正在重建行.

题:

有没有人有解释,也许需要注意这个问题?

堆栈跟踪

其中:getPipelinecheckSearchResults是检索返回表的列表的调用,这会导致生成器被调用

堆栈跟踪

我已经看过了:

我已经阅读了以下文章/ SO问题,但没有更好地理解为什么上述(第一)解决方案的工作原理.

每次我在dataTable中单击commandButton时都会重新创建ViewScoped bean

为什么@PostConstruct回调每次都会触发,即使bean是@ViewScoped?JSF

如何将选定的行传递给dataTable中的commandLink?

http://balusc.blogspot.de/2010/06/benefits-and-pitfalls-of-viewscoped.html

https://java.net/jira/browse/JAVASERVERFACES-1492

Mat*_*yas 1

我找到了 jsf/primefaces/ee-api/glassfish 等的一些来源来调试行为,所以这里是答案:

简而言之

如果一个component

  • 触发导致重定向的操作 ( controller.method)
  • 并被放置在一个datatable
  • 并且datatable根据@ViewScopedbean生成行

然后:

  • controller.method调用后,@ViewScopeddatatable依赖的bean将被重新生成(当然还有它的所有依赖项)

测试:在2.1.7JSF 版本中。查看 的来源2.1.19,我预计那里会有相同的行为。

细节

对于那些在孤独的夏夜大声哭泣并问:“为什么? ”的人

导致此行为的“事件”链(参考来源):

  1. 用户单击表行内的按钮。
  2. 数据被POST发送到服务器
  3. 1-4期按计划进行
  4. APPLICATION_INVOCATION
    1. 单击事件由 JSF 接收。重要提示:引用按钮的单击事件包装在一个事件中,该事件包含有关表和单击发生的行号的信息。为了简单起见:rowEvent & clickEvent
    2. 该事件在组件树中“广播”@UIViewRoot:794
    3. javax.faces.UIDataorg.primefaces.component.datatable.DataTable支持的祖父母p:datatable开始处理事件@UIData.broadcast(FacesEvent)
      1. broadcast方法首先保存最后选择的行的索引
      2. 然后它选择由rowEvent
      3. 将 分派clickEvent给孩子UIComponent,在我们的例子中分派给Button
        1. 一切都很好,事件开始由ActionListener.processAction(ActionEvent)
          1. 这反过来又调用controller.method它返回一个重定向String,事情开始走下坡路
          2. 在该方法的最后,redirectString由一个处理NavigationHandler
            1. 这看到我们即将重定向,很快就清除了第 行中的ViewMap所有bean 。如果我们仔细想想,这是合乎逻辑的,因为我们正在走出去。@ViewScoped179
      4. 回到UIData.broadcast哪个
        • 广播了内部事件后,
        • 不知道某些内部事件导致了重定向,并且它所做的一切都将被扔进垃圾箱(因为302
        • 作为最后一个操作,尝试选择在步骤中保存索引的行4.3.1
      5. 当然,要选择一行,它需要知道该行的数据,这就是@ViewScoped重新生成表所需的 bean 的地方。

结束

注意

虽然我还没有测试过,但我期望有相同的行为,,,,,h:datatable等等。简而言之,每个子类化并且不提供感知方法的组件。p:accordionPanelp:carouselp:galleriap:dataGridUIDataredirectbroadcast