【问题标题】:@ApplicationScoped beans get constructed more than once@ApplicationScoped bean 不止一次被构造
【发布时间】:2019-04-21 21:54:00
【问题描述】:

我有两个托管 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 类的另一个实例。这怎么可能?在this tutorial 我发现了以下内容:

@ApplicationScoped

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

所以我希望每个 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 的标签

所以经过几个小时的调查,我完全没有想法。你有什么提示吗?

【问题讨论】:

    标签: jsf dependency-injection managed-bean


    【解决方案1】:

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

    因为两个完全不知道彼此的不同框架被指示管理(实例化和使用)它。

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

    因此,实际上,您有一个由 JAX-RS 管理的 SyncService 实例,并且您有另一个由 JSF 管理的 SyncService 实例,并且仅在此实例中,也是特定于 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 注解。

    另见:

    【讨论】:

    • 感谢您的详尽解释。我们正在开展一个新项目,我正在学习相关技术。我们被建议使用JSF,但我不知道@Path 注释是另一个框架的一部分。另外,我不知道@ManagedBean 已被弃用,因为在JSF 中搜索依赖注入我主要找到了使用此注释的示例。无论如何,感谢您的解释和所有链接:我将尝试选择一个框架并始终如一地使用它。
    • @Giorgio:谷歌在这方面做得很糟糕,因为如果每个人都只点击文章,他们在搜索引擎中的排名就会越来越高。我越来越多地尝试找到一个共同的权威人工维护站点(例如jsf.zeef.com)并使用它。是的,我使用谷歌,但查找最近的(版本明智和日期明智)链接)我也为其他有趣的主题(如太阳能等)这样做......
    • @BalusC 能给我你的邮箱吗?
    猜你喜欢
    • 2012-10-12
    • 1970-01-01
    • 1970-01-01
    • 2012-12-23
    • 2013-08-25
    • 1970-01-01
    • 2011-05-19
    • 1970-01-01
    相关资源
    最近更新 更多