@ApplicationScoped Bean的构造不止一次

时间:2018-11-19 14:02:55

标签: jsf dependency-injection managed-bean

我有两个托管Java Bean:

import javax.annotation.PostConstruct;
import javax.faces.bean.ApplicationScoped;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;

import javax.ws.rs.Path;

@Path("/sync")
@ManagedBean(name="syncService", eager=true)
@ApplicationScoped
public class SyncService {
    @ManagedProperty(value="#{ldapDirectoryAccess}")
    private DirectoryAccess directoryAccess;

    public void setDirectoryAccess(DirectoryAccess directoryAccess) {
        System.out.println("SyncService.setDirectoryAccess()");
        this.directoryAccess = directoryAccess;
    }

    public SyncService() {
        System.out.println("SyncService() - constructed: " + this);
    }

    @PostConstruct
    public void init() {
        System.out.println("DirectoryAccess injected: " + directoryAccess + " in: " + this);
    }
    ...
}

@ManagedBean(name="ldapDirectoryAccess", eager=true)
@ApplicationScoped
public class LdapDirectoryAccess implements DirectoryAccess {
    public LdapDirectoryAccess() {
        System.out.println("LdapDirectoryAccess constructed: " + this);
    }
    ...
}

在Tomcat中部署应用程序时,在catalina.out中得到以下输出:

SyncService() - constructed: ...SyncService@705ebb4d
...
LdapDirectoryAccess constructed: ...LdapDirectoryAccess@3c1fd5aa
SyncService.setDirectoryAccess()
DirectoryAccess injected: ...LdapDirectoryAccess@3c1fd5aa in:
                          ...SyncService@705ebb4d
LdapDirectoryAccess constructed: ...LdapDirectoryAccess@59d6a4d1

因此,首先按预期构造每个bean的实例,然后将第二个bean注入第一个bean。但是随后,创建了第二个bean类的另一个实例。这怎么可能?在this tutorial中,我发现了以下内容:

  

@ApplicationScoped

     

只要Web应用程序存在,Bean就会存在。它创建于   在应用程序中(或何时)涉及此bean的第一个HTTP请求   Web应用程序启动,并在其中设置eager = true属性   @ManagedBean),并在网络应用程序关闭时被销毁。

因此,我希望在启动应用程序时创建每个Bean的一个实例,并在关闭应用程序时销毁两个实例。但是LdapDirectoryAccess被构造了两次。

此外,当我打开SyncService所服务的页面时,我看到:

SyncService() - constructed: ... SyncService@1cb4a09c

所以也建立了SyncService的第二个实例,我不明白为什么。另外,这次没有注入directoryAccess属性,该服务将引发空指针异常。

这意味着SyncService的第一个实例是正确构建的,但是随后

  1. 创建了SyncService的第二个实例(为什么?)
  2. 没有LdapDirectoryAccess注入其中(为什么?)
  3. SyncService的第二个实例用于提供对我的REST API的调用。为什么使用此实例而不是创建的第一个实例?

但是,我查看了this question及其答案:

  • 我正在使用Mojarra 2.2.18
  • 我的应用程序的web.xml不包含任何提及com.sun.faces.config.ConfigureListener的标签

因此,经过几个小时的调查,我完全没主意了。你有什么提示吗?

1 个答案:

答案 0 :(得分:4)

  

创建了SyncService的第二个实例(为什么?)

因为要指示两个完全不了解的框架来进行管理(实例化和使用)。

  1. JAX-RS,通过@Path
  2. JSF,通过@ManagedBean

因此,实际上,您有一个SyncService实例,该实例由JAX-RS管理,而您有另一个SyncService实例,该实例由JSF管理,仅在这种情况下,还可以识别特定于JSF的@ManagedProperty。 JAX-RS无法理解@ManagedProperty,因此对此无能为力。

基本上,您在这里将JAX-RS资源和JSF托管Bean紧密耦合在同一类中。紧密耦合是不好的编程习惯。您需要将SyncService分成一个独立的JAX-RS资源和一个独立的JSF托管bean。并且您需要将LdapDirectoryAccess转换为使用JAX-RS和JSF都可以识别的另一个bean管理框架,以便可以在两个实例中注入一个实例。在现代Java EE 8中,它将是由CDI的@javax.enterprise.context.ApplicationScoped管理的bean。在旧版Java EE 6/7中,您可能会为此滥用EJB的@javax.ejb.Singleton

鉴于您仍在使用不推荐使用的@ManagedBean而不是其替换@Named,我假设您仍在使用旧版Java EE,因此我仅展示EJB方法

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;

@Singleton
public class LdapDirectoryAccessService implements DirectoryAccess {

    @PostConstruct
    public void init() {
        System.out.println("LdapDirectoryAccess constructed: " + this);
    }
}

import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.ws.rs.Path;

@Path("/sync")
public class SyncResource {

    @EJB
    private DirectoryAccess directoryAccess;

    @PostConstruct
    public void init() {
        System.out.println("DirectoryAccess injected: " + directoryAccess + " in: " + this);
    }
}

import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.faces.bean.RequestScoped;
import javax.faces.bean.ManagedBean;

@ManagedBean
@RequestScoped
public class SyncBacking {

    @EJB
    private DirectoryAccess directoryAccess;

    @PostConstruct
    public void init() {
        System.out.println("DirectoryAccess injected: " + directoryAccess + " in: " + this);
    }
}

请注意,要在JAX-RS资源中注入EJB,可能需要在Java EE 6/7中进行其他配置,为此请参见下面列表的第一个链接。并且,如果您希望LdapDirectoryAccessService在服务器启动期间急于初始化,请添加@javax.ejb.Startup批注。

另请参见: