我有两个托管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
的第一个实例是正确构建的,但是随后
SyncService
的第二个实例(为什么?)LdapDirectoryAccess
注入其中(为什么?)SyncService
的第二个实例用于提供对我的REST API的调用。为什么使用此实例而不是创建的第一个实例?但是,我查看了this question及其答案:
web.xml
不包含任何提及com.sun.faces.config.ConfigureListener
的标签因此,经过几个小时的调查,我完全没主意了。你有什么提示吗?
答案 0 :(得分:4)
创建了
SyncService
的第二个实例(为什么?)
因为要指示两个完全不了解的框架来进行管理(实例化和使用)。
@Path
@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
批注。