You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jakarta CDI:能否为同一类注入两个不同单例实例?

关于Jakarta CDI注入同一类不同单例实例的问题

我是依赖注入框架的新手。我的问题本身看似矛盾,但假设场景下,能否通过Jakarta CDI在代码中注入同一类的两个不同实例?也就是所谓的同一类的两个单例?如果可以,对应的注解该如何编写?是否需要通过@Produces方法生成两个不同的@Named实例?

补充代码与问题

以下是我尝试的实现代码:

@Singleton
public final class StartupBean {
  @Inject
  StartupBean(SomeManager someManager) {
    // nothing happening here
  }

  @Produces
  @Named("fooObject")
  public MyImplementation produceFooObject() {
    return new MyImplementation();
  }

  @Produces
  @Named("barObject")
  public MyImplementation produceBarObject() {
    return new MyImplementation();
  }
}


// class implementing two interfaces
final class MyImplementation 
    extends SomethingElse
    implements MyInterface, MyOtherInterface {
}

// manager class using both named instances
@Singleton
final class SomeManager {
  private final MyInterface foo;
  private final MyInterface bar;

  @Inject
  SomeManager(
      @Named("fooObject") MyInterface fooObject,
      @Named("barObject") MyInterface barObject) {
    this.foo = fooObject;
    this.bar = barObject;
    // stuff happening
  }
}

// class using one of the object with different type
public final class ClassA {
  private final MyOtherInterface bla;

  @Inject
  ClassA(@Named("fooObject") MyOtherInterface fooObject) {
    this.bla = fooObject;
  }
}

这段代码引发了StackOverFlowError异常,请问有什么解决建议?


问题分析与解决建议

1. 栈溢出的核心原因:循环依赖

你的代码出现StackOverFlowError是因为存在循环依赖链:

  • StartupBean是@Singleton,构造器注入了SomeManager
  • SomeManager同样是@Singleton,构造器需要的MyImplementation实例由StartupBean的@Produces方法提供
  • CDI容器初始化StartupBean时需要先创建SomeManager,而创建SomeManager又需要StartupBean先完成初始化以调用生产方法,形成无限循环

2. 正确实现同一类不同单例实例的方式

你用@Produces+@Named区分同一类不同实例的思路是可行的,但需要调整实现打破循环依赖:

方案一:拆分独立的生产者类

把@Produces方法从StartupBean中抽离到独立类,切断循环依赖:

// 独立生产者类,无额外依赖
public class MyImplementationProducer {
  @Produces
  @Named("fooObject")
  @Singleton // 标记为单例,确保每个命名实例仅生成一次
  public MyImplementation produceFooObject() {
    return new MyImplementation();
  }

  @Produces
  @Named("barObject")
  @Singleton
  public MyImplementation produceBarObject() {
    return new MyImplementation();
  }
}

// 修改StartupBean,移除生产者方法,按需调整依赖
@Singleton
public final class StartupBean {
  // 若不需要依赖SomeManager,直接用无参构造
  public StartupBean() {
    // 初始化逻辑
  }

  // 若确实需要SomeManager,可改用字段注入(避免构造器循环)
  // @Inject
  // private SomeManager someManager;
}

方案二:使用自定义限定符(更规范的CDI实践)

相比@Named,自定义限定符注解可读性更强,是CDI区分实例的推荐方式:

// 自定义限定符:Foo
@Qualifier
@Target({TYPE, METHOD, PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Foo {}

// 自定义限定符:Bar
@Qualifier
@Target({TYPE, METHOD, PARAMETER, FIELD})
@Retention(RUNTIME)
public @interface Bar {}

// 独立生产者类
public class MyImplementationProducer {
  @Produces
  @Foo
  @Singleton
  public MyImplementation produceFooObject() {
    return new MyImplementation();
  }

  @Produces
  @Bar
  @Singleton
  public MyImplementation produceBarObject() {
    return new MyImplementation();
  }
}

// 使用时直接用自定义限定符注入
@Singleton
final class SomeManager {
  private final MyInterface foo;
  private final MyInterface bar;

  @Inject
  SomeManager(@Foo MyInterface fooObject, @Bar MyInterface barObject) {
    this.foo = fooObject;
    this.bar = barObject;
  }
}

3. 额外注意事项

  • 不要给MyImplementation类添加@Singleton或其他作用域注解,否则CDI会优先使用类级作用域,忽略生产者方法的配置
  • 如果MyImplementation需要依赖其他Bean,不要直接用new创建,应在生产者方法中注入依赖:
    @Produces
    @Foo
    @Singleton
    public MyImplementation produceFooObject(Dependency requiredDep) {
      return new MyImplementation(requiredDep);
    }
    

内容的提问来源于stack exchange,提问作者numberfourteen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 01:40:11