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

32

我在页面上使用Datatable,并使用绑定属性将其绑定到我的后端bean。这是我的代码:

<?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://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.prime.com.tr/ui">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
            <h:form prependId="false">

                <h:dataTable var="item" value="#{testBean.stringCollection}" binding="#{testBean.dataTable}">
                    <h:column>
                        <h:outputText value="#{item}"/>
                    </h:column>
                    <h:column>
                        <h:commandButton value="Click" actionListener="#{testBean.action}"/>
                    </h:column>
                </h:dataTable>

            </h:form>

    </h:body>
</html>

这是我的"bean" :-

package managedBeans;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.component.html.HtmlDataTable;

@ManagedBean(name="testBean")
@ViewScoped
public class testBean implements Serializable {

    private List<String> stringCollection;

    public List<String> getStringCollection() {
        return stringCollection;
    }

    public void setStringCollection(List<String> stringCollection) {
        this.stringCollection = stringCollection;
    }

    private HtmlDataTable dataTable;

    public HtmlDataTable getDataTable() {
        return dataTable;
    }

    public void setDataTable(HtmlDataTable dataTable) {
        this.dataTable = dataTable;
    }

    @PostConstruct
    public void init(){
        System.out.println("Post Construct fired!!");
        stringCollection = new ArrayList<String>();
        stringCollection.add("a");
        stringCollection.add("b");
        stringCollection.add("c");

    }

    public void action(){
        System.out.println("Clicked!!");

    }
}
请告诉我为什么每次我点击按钮时 @PostConstruct 都会触发?我的 bean 是 @ViewScoped,因此只要我在同一页上,它应该只触发一次。如果我删除绑定属性,则一切正常,@PostConstruct 回调仅触发一次。那我使用绑定属性时为什么每次都会触发呢?我需要绑定属性并希望执行初始化任务,例如从 Web 服务中获取数据等,仅一次。我该怎么办?在哪里编写我的初始化任务?

这个问题有解决方案吗?我也遇到了同样的问题... - Julio Faerman
5个回答

34

有趣的是,当您在视图作用域Bean上使用组件绑定时,视图作用域会中断。

我不确定这是否是JSF2中的一个错误,我首先要阅读整个JSF2规范。目前最好的方法是放弃组件绑定,并通过新的EL 2.2方法参数语法传递所选项目:

<h:dataTable var="item" value="#{testBean.stringCollection}">
    <h:column>
        <h:outputText value="#{item}"/>
    </h:column>
    <h:column>
        <h:commandButton value="Click" action="#{testBean.action(item)}"/>
    </h:column>
</h:dataTable>

参见:


更新 (2012年12月): 这确实是JSF2中的一个错误。这是一个鸡生蛋问题。视图作用域bean存储在JSF视图状态中。因此,在恢复视图阶段之后才能使用视图作用域bean。但是,binding属性在恢复视图阶段运行,而此时视图作用域bean尚不可用。这会导致创建全新的视图作用域bean实例,然后再被存储在恢复的JSF视图状态中的真正的视图作用域bean替换。

这被报告为JSF问题1492JSF规范问题787,将在JSF 2.2中修复。在此之前,您最好专门对请求作用域bean使用binding或寻找特定功能要求的替代方法。


更新 (2015年3月): JSF 2.2修复已回退到Mojarra 2.1.18。因此,如果您仍在使用JSF 2.0/2.1,则最好升级至至少该版本。另请参见JSF中的组件绑定是什么?何时最好使用它?JSF2 Facelets中的JSTL...有意义吗?等。


嗯! :) 那也是 datatable 的一个错误吗? - TCM
请查看更新后的回答,DataModel#getRowData()是更好的选择。 - BalusC
2
嗨,BalusC,我读了你关于ViewScoped的文章,也看了这些帖子。我有两个问题想问你。首先:为什么人们要将表格绑定到托管bean上。在这个例子中,我认为他提到绑定dataTable以初始化其值,但是您可以在@PostConstruct中初始化对象列表,并在dataTable中显示它们。这样不是可以达到相同的结果吗?其次:在您的项目中,您使用了DataModel,因此,如果您将要在dataTable中显示的对象列表包装在DataModel中,那么无论用户选择哪一行,只要调用getRowData(),就会知道选中的行是哪一个? - Thang Pham
3
  1. 这是JSF 1.x的遗留问题。
  2. 是的。在动作方法中只需使用getRowData()即可获取执行动作的行。
- BalusC
嗨Balus,对于viewScope中的问题有任何解决方案吗?谢谢...我很抱歉我的英语。 - Marco R
显示剩余2条评论

4

正如其他人所说,我认为最好的做法是放弃组件绑定(在这里不需要它)。

但我想补充一点,您可以使用操作参数以更面向对象的方式实现与您尝试做的相同效果,像这样:

<h:commandButton value="Click" action="#{testBean.action(item)}"/>

在你的Java代码中:

  public void action(Item item){
    System.out.println("Clicked!!" + item);
}

1
需要注意的是,这需要一个Servlet 3.0 / EL 2.2能力的容器。JSF 2.0被设计为向后兼容Servlet 2.5,当您在Servlet 2.5容器上运行JSF 2.0时可能会失败。有关所有方法,请参见此答案 - BalusC
2
我认为它需要EL 2.2,但不需要Servlet 3.0。直到上周,我们在Glassfish 2上运行EL 2.2,它没有Servlet 3.0,但它工作得很好。 - ymajoros

0

balusc的回答对我帮助很大,我想说我曾经在mojarra 2.1.7版本中遇到过这个bug,现在我正在使用2015年1月发布的2.1.29-01版本,这个bug已经被修复了。我的问题是将tabview绑定到一个viewscoped bean上。使用这个版本后,我没有遇到这个bug,绑定和postconstruct也正常工作。

我使用Jboss 5.2,必须使用mojarra 2.1.x,所以我希望这个答案能帮助其他处于同样情况下的人。

http://mvnrepository.com/artifact/com.sun.faces/jsf-api/2.1.29-01 http://mvnrepository.com/artifact/com.sun.faces/jsf-impl/2.1.29-01


0

其他解决方案:

  • 将HtmlDataTable绑定到请求范围的bean中。
  • 在视图范围的bean中注入此请求范围的bean。

JBoss Seam使用此解决方案将JSF组件绑定到会话范围组件。


0
如果您有一个视图作用域的bean,并且想要保留在表单中输入的值或者不想让postconstruct被触发,那么您应该从您的操作方法中返回null。
如果您返回某些结果(例如无效),然后使用faces-config.xml将无效结果指向同一页,则视图作用域bean会被重新创建,从而导致postconstruct再次触发。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接