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,构造器注入了SomeManagerSomeManager同样是@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
相关产品推荐
相关产品推荐

