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

时间:2013-05-27 20:02:43

标签: 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>

控制器:

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

制片人

(使用here所述的@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;
   }
}

问题&amp;其场景

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

    简而言之:点击后,将id复制到数据表外部,然后触发点击提交按钮。

    在表格栏中h:commandButton添加了:

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

    在桌子外面:

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

    最后它起作用了,但我无法弄清楚是什么原因导致了第一个&amp;重新初始化视图范围bean的基本方法。查看堆栈跟踪(见下文),似乎正在重建行。

    问题:

    是否有人有解释,并且可能需要注意以解决此问题?

    堆栈跟踪

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

    stack trace

    我已经看过了:

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

    ViewScoped bean is recreated everytime I click on commandButton in my dataTable

    Why does @PostConstruct callback fire every time even though bean is @ViewScoped? JSF

    How can I pass selected row to commandLink inside dataTable?

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

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

2 个答案:

答案 0 :(得分:1)

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

简而言之

如果是component

  • 触发导致重定向
  • 的操作(controller.method
  • 并放置在datatable
  • datatable根据@ViewScoped bean
  • 生成行

然后:

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

经过测试:在JSF的版本2.1.7中。 查看2.1.19的来源,我希望在那里有相同的行为。

详细

对于那些在寂寞的夏夜大声喊叫的人:“为什么?

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

  1. 用户单击表格行内的按钮。
  2. 数据POST已添加到服务器
  3. 阶段1-4按计划进行
  4. APPLICATION_INVOCATION
    1. JSF收到click事件。 重要说明:引用该按钮的点击事件包含在一个事件中,该事件包含有关表格和信息的信息。点击发生的行号。为简单起见:rowEvent & clickEvent
    2. 该事件在组件@ UIViewRoot:794
    3. 的树中“广播”
    4. javax.faces.UIData org.primefaces.component.datatable.DataTable支持p:datatable的祖父母开始处理活动@ UIData.broadcast(FacesEvent)
      1. broadcast方法首先保存最后一个选定行的索引
      2. 然后选择rowEvent
      3. 指定的那个
      4. clickEvent的案例中调度UIComponent上的Button {}}}
        1. 一切顺利&amp;很好,事件开始由ActionListener.processAction(ActionEvent)处理
          1. 这反过来调用controller.method,返回重定向String,事情开始走下坡路
          2. 在方法结束时,redirectStringNavigationHandler处理
            1. 这个看到我们即将快速重定向的人会清除ViewMap@ViewScoped行删除所有179个bean。如果我们考虑它是合乎逻辑的,因为我们正在走出去。
      5. 返回UIData.broadcast
        • 播放了内部事件,
        • 不知道某个内部事件导致重定向,它所做的一切都会被扔到垃圾箱里(因为302
        • 作为最后一个操作,尝试选择在步骤4.3.1
        • 中保存其索引的行
      6. 当然要选择一行,它需要知道它的数据,这就是表所需的@ViewScoped bean重新生成的地方。
  5. 结束

    注意

    虽然我没有经过测试,但我希望同样的行为h:datatablep:accordionPanelp:carouselp:galleriap:dataGrid等等。子类UIData的子组件,但不提供redirect - 知道broadcast方法。

答案 1 :(得分:0)

除非我错误地理解这一点,否则你正在使用一个以某种方式限定在视图范围内的bean(Seam 3,CODI或你自己编写的自定义范围)。只要JSF生命周期在相同的视图上运行(这是一个正确的假设),你就没事了,但是当你更改视图id时你会得到视图范围bean的新实例,你会感到惊讶吗?视图范围的整个目的是在JSF中保持相同的视图状态,只要您告诉JSF转到不同的视图ID,它就会创建一个新的视图状态。听起来你真正想要的是谈话范围。