JSF管理的bean中列表的初始化

12

我有一个关于在POJO中初始化List的问题,代码如下:

public class Person {

 //other fields...
 private List<String> friends=new ArrayList<>();

     public List<String> getFriends() {
        return friends;
     }
     public void setFriends(List<String> friends) {
        this.friends = friends;
    }

}

还是像这样更好,将初始化放在其他类中(例如Bean(JSF))

public class Person {

 //other fields...
 private List<String> friends;

     public List<String> getFriends() {
        return friends;
     }
     public void setFriends(List<String> friends) {
        this.friends = friends;
    }

}

所以我的问题是哪种方法更好?


7
如果他们先调用getter方法,你就会出问题。 - Sotirios Delimanolis
它是一个“managedBean”吗? - Eng.Fouad
是的,它是managedBean。 - Mitja Rogl
1
@kolossus 我们之前不知道这是一个托管的Bean。鉴于新的信息,我同意你的看法,所以删除了我的评论。 - Kevin Crowell
1
实际上,您不需要始终依赖于@PostConstruct。只有在需要等待某些依赖注入完成以便使用某些服务来初始化列表时,才需要该注释。如果没有,那么您在问题中所做的就很好 :)。但是,我有一种不好的感觉,即您当前的架构不是最佳实践。您的数据库中是否有名为“Person”的实体? - Mr.J4mes
显示剩余6条评论
5个回答

26
如果它是一个托管的bean,正如你所说,你应该在一个使用@PostConstruct注释的方法中执行此操作。
public class Person {
    private List<String> friends;
    @PostConstruct
    public void init(){
         friends = new ArrayList<String>();
    }

    //getter and setter...
}
  1. 在JSF的上下文中,通常不建议在getter和setter中进行任何初始化操作。请参阅为什么JSF会多次调用getter

  2. 此外,根据@PostConstruct的API规范,合同规定了安全功能和保证,如果在标记为这样的方法中抛出异常,则不应将bean置于服务中。在普通构造函数中没有这样的保证。

  3. 在托管bean中,注入发生在构造之后立即进行。这意味着您在构造函数中执行的任何操作都不能依赖于通过@ManagedProperty注入的任何资源。而在@PostConstruct方法中,您将可以访问托管bean上声明的所有资源。

编辑:重要的是要注意,对于任何@ManagedBean,只能有一个@PostConstruct,因此所有重要的初始化都应在其中进行。

值得注意的是,尽管 @PostConstruct 方法是初始化支持 bean 变量/ List 的理想位置,但与托管 bean 的范围相关的影响也需要考虑。
  1. @RequestScoped:使用此注释的托管bean,该方法将在JSF视图提交时被调用。 @RequestScoped bean会在每个请求中销毁并重新创建,这意味着根据您的设置,在每个请求期间初始化的列表可能会重置为空或默认值。在某些情况下,由于在JSF请求期间重新初始化列表,可能会发生转换错误。

  2. @ViewScoped:在具有此注释的托管bean中,只有当处理与@ViewScoped bean相同的实例时,才保证运行@PostConstruct方法一次。如果视图作用域bean被销毁并重新创建,则@PostConstruct方法将再次运行。

  3. @SessionScoped:此注释的bean仅创建一次,并保持活动状态,直到用户的HTTP会话结束。在这种情况下,保证只运行一次@PostConstruct方法,直到销毁bean。

另请参阅


请不要编辑我引用的链接。我需要它作为Meta上的参考。 - kolossus
在你的答案中加入这句话是值得的(直接来自于@PostConstruct文档):只能有一个方法被标记为该注解。 - Luiggi Mendoza
1
非常好的 JSF 方面的观点,像往常一样,但在这种情况下,我完全看不出使用 @PostConstruct 覆盖构造函数或初始化块的任何理由。首先,我建议仅出于使用具有相关注释的初始化依赖项的目的而使用后置构造方法。我建议在构造函数或初始化块中独立于注入的依赖项初始化 bean 属性,因为它们自然属于那里。 - skuntsel

4
我建议这样做:
public class Person {
     //other fields...
     private List<String> friends=new ArrayList<>();

     // returns a copy to protect original list
     public List<String> getFriends() {
        Collections.unmodifiableList(new ArrayList<>(friends));
     }
     public void addFriend(String> friend) {
        this.friends.add(friend);
     }
     public void addFriends(List<String> friends) {
        this.friends.addAll(friends);
     }
}

你可能想要在set中使用clear或者进行防御性拷贝,否则它就不是一个真正的setter,而更像是一个addAll - Boris the Spider
@bmorris591:谢谢,我已经更改了方法名称以避免混淆。 - anubhava
1
非常类似于我的答案,但是也许总体设计更好的方法是像这样拥有 add 和 addAll 方法。我猜我们可能同时在打字。+1 - cobaltduck

3

在我看来,最好在构造函数中处理这个问题。如果使用默认构造函数,请在构造函数中初始化列表。

public Person() {
    friends = new ArrayList<>();
}

如果使用接受参数的构造函数,则让调用类传递一个列表。
public Person(ArrayList<> friends) {
    this.friends = friends;//friends
}

在托管 Bean 中,最好不要在构造函数中处理此问题。 - kolossus
是的,我的示例对于POJO而言比托管bean更有效。虽然我认为如果您不使用托管属性,则在默认无参构造函数中初始化托管bean没有任何害处。 - Haz
由于这是一个基本示例,它将起作用。但是,正如kolossus答案的第3节中所发布的那样,在托管bean上注入的任何字段(例如@EJB@ManagedProperty等)都不会在类构造函数中起作用,而是在@PostConstruct方法中起作用。 - Luiggi Mendoza

2

我的建议是,在getter方法中添加一个null检查:

public class Person {
  //other fields...
  private List<String> friends;

  public List<String> getFriends() {
     if (this.friends == null) friends = new ArrayList<String>();
     return friends;
  }
}

但是请注意我省略了setter。相反,在任何客户端代码中,像这样调用:

personInstance.getFriends().add("Some Item");

或者如果您有完整的列表需要添加:

personInstance.getFriends().addAll(someStringCollection);

取决于原帖作者想要的行为。这是一种被称为“懒加载”的好策略。如果这将成为托管bean,则还需要一个无参构造函数。 - Richard Wеrеzaк

1

这要看情况。通常第一种方式更可取,因为你可能会在以后向集合中添加一些内容。如果你不知道集合是否初始化,那么每次都必须进行检查。


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