【问题标题】:Multiple instances of stateful EJB when injecting via CDI通过 CDI 注入时的多个有状态 EJB 实例
【发布时间】:2025-11-27 10:40:01
【问题描述】:

这主要是关于了解使用@Inject 注入有状态的EJB (SFSB) 与使用@EJB 注入它时的差异

主要区别之一应该是通过@Inject 注入时的上下文意识。所以我的假设是,如果我创建两个@RequestScoped bean 并在每个中注入一个SFSB 两次(一次使用@Inject,一次使用@EJB),则SFSB 通过@ 注入987654332@ 将是两个@RequestScoped bean 中的相同实例,而通过@EJB 注入的那些将是不同的实例

这个假设似乎是错误的,但我不明白为什么。 CDI 不应该知道两个 bean 都是 @RequestScoped 并因此注入相同的 SFSB 吗?为什么不是这样,还是我的测试代码有些缺陷?

这是我的SFSB 及其界面:

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.Local;
import javax.ejb.PrePassivate;
import javax.ejb.Stateful;

import package.MyStateful;

@Stateful
@Local(MyStateful.class)
public class MyStatefulImpl implements MyStateful {

    @PostConstruct
    private void postConstruct() {
            System.out.println("SFSB postconstruct ref: " + this.toString());
    }

    @PreDestroy
    private void preDestroy() {
            System.out.println("SFSB predestroy ref: " + this.toString());
    }

    @PrePassivate
    private void prePassivate() {
            System.out.println("SFSB prepassivate ref: " + this.toString());
    }

    @Override
    public String myToString() {
            return toString();
    }
}

public interface MyStateful {
    String myToString();
}

这是一个@RequestScoped bean:

import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;

import package.MyStateful;

@Named
@RequestScoped
public class MyFirstRequestScoped {

    @Inject
    MyStateful myStatefulByCDI;

    @EJB
    MyStateful myStatefulByEJB;

    public MyStateful getMyStatefulByCDI() {
            System.out.println("first#myStatefulByCDI proxy ref: " + myStatefulByCDI.toString());
            System.out.println("first#myStatefulByCDI stateful ref: " + myStatefulByCDI.myToString());
            return myStatefulByCDI;
    }

    public MyStateful getMyStatefulByEJB() {
            System.out.println("first#myStatefulByEJB proxy ref: " + myStatefulByEJB.toString());
            System.out.println("first#myStatefulByEJB stateful ref: " + myStatefulByEJB.myToString());
            return myStatefulByEJB;
    }

}

还有一个名为 MySecondRequestScoped@RequestScoped bean 具有类似的实现。

当这些通过ELJSF xhtml 页面调用时(没什么特别的,只是一个<h:outputText value="#{myFirstRequestScoped.myStatefulByCDI}" /> 等等来触发它们的创建),这是控制台输出(WebSphere ApplicationServer 8.5.5.0):

[1/4/14 12:39:11:759 CET] 000000dc SystemOut     O SFSB postconstruct ref: package.MyStatefulImpl@c03fcdee
[1/4/14 12:39:11:761 CET] 000000dc SystemOut     O SFSB postconstruct ref: package.MyStatefulImpl@36b3bb10
[1/4/14 12:39:11:761 CET] 000000dc SystemOut     O first#myStatefulByCDI proxy ref: package.EJSLocal0SFMyStatefulImpl_8d170245@48da7f98(BeanId(Project#ProjectEJB.jar#MyStatefulImpl, 5D0CBA11-0143-4000-E000-6A007F000001))
[1/4/14 12:39:11:762 CET] 000000dc SystemOut     O first#myStatefulByCDI stateful ref: package.MyStatefulImpl@36b3bb10
[1/4/14 12:39:11:768 CET] 000000dc SystemOut     O SFSB postconstruct ref: package.MyStatefulImpl@9b3971c7
[1/4/14 12:39:11:768 CET] 000000dc SystemOut     O SFSB postconstruct ref: package.MyStatefulImpl@456cec27
[1/4/14 12:39:11:769 CET] 000000dc SystemOut     O second#myStatefulByCDI proxy ref: package.EJSLocal0SFMyStatefulImpl_8d170245@48da7fa1(BeanId(Project#ProjectEJB.jar#MyStatefulImpl, 5D0CBA18-0143-4000-E001-6A007F000001))
[1/4/14 12:39:11:769 CET] 000000dc SystemOut     O second#myStatefulByCDI stateful ref: package.MyStatefulImpl@456cec27
[1/4/14 12:39:11:769 CET] 000000dc SystemOut     O first#myStatefulByEJB proxy ref: package.EJSLocal0SFMyStatefulImpl_8d170245@48da7f9b(BeanId(Project#ProjectEJB.jar#MyStatefulImpl, 5D0CBA0E-0143-4000-E000-6A007F000001))
[1/4/14 12:39:11:769 CET] 000000dc SystemOut     O first#myStatefulByEJB stateful ref: package.MyStatefulImpl@c03fcdee
[1/4/14 12:39:11:769 CET] 000000dc SystemOut     O second#myStatefulByEJB proxy ref: package.EJSLocal0SFMyStatefulImpl_8d170245@48da7fa1(BeanId(Project#ProjectEJB.jar#MyStatefulImpl, 5D0CBA18-0143-4000-E000-6A007F000001))
[1/4/14 12:39:11:770 CET] 000000dc SystemOut     O second#myStatefulByEJB stateful ref: package.MyStatefulImpl@9b3971c7
[1/4/14 12:39:11:848 CET] 000000dc SystemOut     O SFSB predestroy ref: package.MyStatefulImpl@36b3bb10
[1/4/14 12:39:11:849 CET] 000000dc SystemOut     O SFSB predestroy ref: package.MyStatefulImpl@456cec27
[1/4/14 12:50:11:765 CET] 00000120 SystemOut     O SFSB prepassivate ref: package.MyStatefulImpl@c03fcdee
[1/4/14 12:50:11:766 CET] 00000120 SystemOut     O SFSB prepassivate ref: package.MyStatefulImpl@9b3971c7

看来:

  • 创建了 4 个 SFSB 实例;我本来预计这将是只有 3。那些通过@EJB 注入的人不知道上下文,所以我认为如果为每个注入点创建它们就可以了。但是由于CDI 应该知道上下文(@RequestScoped),我认为CDI 会重新注入已经创建的SFSB
  • @Inject@EJB 之间的唯一区别似乎在于,当通过 CDI 注入时,生命周期是自动管理的 - 为那些调用注解 @PreDestroy 的方法 (36b3bb10 和 456cec27)。通过@EJB(c03fcdee 和 9b3971c7)注入的这些后来仅被钝化,并且似乎不会在以后的任何时间被销毁。

后者似乎是使用@Inject而不是@EJB的一个很好的理由,但我不明白的是CDI上下文意识的真正含义,无论范围如何,何时都会创建 SFSB 的新实例?

顺便说一句,使用 @SessionScoped bean 时的行为相同,即使第二个 bean 是在跟随到另一个页面的链接之后创建的(以确保 SFSB 通过@Inject 肯定已经存在)。此外,通过@EJB 注入的SFSB 实例在会话的生命周期内只创建一次,就像通过@Inject 注入的实例一样 - 所以这些似乎知道上下文,也,不知怎的……?当混合@SessionScoped@RequestScoped bean 时,@SessionScoped bean 得到SFSB 的另一个实例,而不是@RequestScoped bean,这很好 - 但似乎不是CDI 的某种特性,因为对于通过@Inject 注入的SFSB 实例以及通过@EJB 注入的实例both 都是如此。

编辑:观察到的行为的结论: 通过@Inject@EJB 注入SFSB 之间的唯一区别似乎是,在前一种情况下,SFSB 在离开作用域时会自动销毁,而在后一种情况下则不会。它是否正确?这让我觉得很奇怪,因为我预计 CDI 的行为会有所不同......

关于我缺少什么的任何提示,即对“CDI”中的“C”有误解?我希望这不是 WebSphere 的“专长”……

【问题讨论】:

  • @EJB 似乎来自与 CDI 不同的技术时代。除其他外,它似乎使用 JNDI 进行查找。
  • 当然,@EJBCDI 存在的时间要长得多,CDI 从版本 6 开始就是 Java EE 的一部分(请参阅 oracle.com/technetwork/java/javaee/tech/…)。但遗憾的是,对我来说,这并不能解释我上面解释的行为。

标签: dependency-injection java-ee-6 cdi ejb-3.1 websphere-8


【解决方案1】:

为了将您的 SFSB 范围限定为请求,您需要为其指定 @RequestScoped 范围。然后你应该看到注入了相同的实例。现在由于这两个都是代理的,最简单的确认方法是从一个 bean 设置一些值,然后从另一个 bean 获取值。

【讨论】:

  • 好的,这有效...检查了 toString ID,所以我知道它是相同的 SFSB。所以就像 “当用 CDI 注入 SFSB 时,为了获得 SFSB 的正确实例,SFSB 必须有一个 Scope 注释”? 我觉得这个令人困惑,因为当我决定更改我的 Web 层以使用另一个范围时(例如,对话而不是请求,因为我拆分了一个页面并使一些进程成为多个页面上的向导),我也必须更改我的 EJB 上的范围注释?我似乎在这里误解了一些概念。您如何以最佳实践方式将SFSBs 与 CDI 一起使用?
  • 范围是适当的。在大多数情况下,我不使用 SFSB,我在我的应用程序中使用无状态。在这些中,我没有提供范围。如果需要,有状态的性质将位于纯 CDI 对象中。
  • 是的,问题来了!我反过来看,我想我会有一个SFSB,正如你所说,如果需要,我会添加一个范围,即CDI。但是,如果我已经有一个范围为 CDI 的 bean 并添加 @Stateful 如果我需要这些功能,这对我来说听起来更合理。我想知道SFSBs 是否有很多用例,现在有CDI,尤其是对于具有Web 界面的应用程序,但这是另一个问题。非常感谢您的回答和解释!
最近更新 更多