Quarkus多模块项目循环引用问题 求CDI依赖注入解决方案
我首次用Quarkus开发多模块项目,父POM的模块结构如下:
<modules> <module>domain</module> <module>application</module> <module>client</module> </modules>
domain模块存放需要由application和client模块实现的接口,因此application和client模块都添加了domain的依赖来实现接口。但尝试在domain模块中添加application的依赖以实现CDI注入时,出现了循环引用错误:
[ERROR] [ERROR] The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='com.test.name:application:0.0.1-SNAPSHOT'}' and 'Vertex{label='com.test.name:domain:0.0.1-SNAPSHOT'}' introduces to cycle in the graph com.test.name:domain:0.0.1-SNAPSHOT --> com.test.name:application:0.0.1-SNAPSHOT --> com.test.name:domain:0.0.1-SNAPSHOT @
[ERROR] The projects in the reactor contain a cyclic reference: Edge between 'Vertex{label='com.test.name:application:0.0.1-SNAPSHOT'}' and 'Vertex{label='com.test.name:domain:0.0.1-SNAPSHOT'}' introduces to cycle in the graph com.test.name:domain:0.0.1-SNAPSHOT --> com.test.name:application:0.0.1-SNAPSHOT --> com.test.name:domain:0.0.1-SNAPSHOT -> [Help 1]
核心原则:domain层不能依赖实现层
domain作为接口、领域模型的定义层,必须保持对实现层(application/client)的独立,循环依赖本质是违反了依赖倒置原则,解决思路是靠CDI的特性完成接口与实现的绑定,而非硬编码依赖。
方案1:@Inject + @Qualifier(推荐)
这是最符合Quarkus CDI设计的方案:
- 在domain模块定义接口和自定义Qualifier(用于区分不同实现):
// domain模块 - 业务接口 public interface BusinessService { void execute(); } // domain模块 - 自定义限定符,标记application层的实现 @Qualifier @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER}) public @interface AppImpl { }
- 在application模块实现接口并标记Qualifier:
// application模块 - 接口实现类 @AppImpl @Singleton // 按需选择CDI作用域,如@RequestScoped public class AppBusinessServiceImpl implements BusinessService { @Override public void execute() { // 业务逻辑实现 } }
- 在需要注入的地方(比如application模块的业务类)直接注入:
// application模块中的类 @Inject @AppImpl BusinessService businessService; // 使用实例 public void process() { businessService.execute(); }
此时application依赖domain,domain不依赖任何实现层,完全避免循环。
方案2:拆分职责,剥离domain中的依赖代码
如果domain模块中存在需要依赖实现类的逻辑,说明domain的职责边界不清晰,应该把这部分代码拆分到新的模块(比如infrastructure或base):
- 新建
infrastructure模块,依赖domain - application模块依赖
infrastructure和domain - 将需要注入实现的代码移到
infrastructure模块,application模块提供实现并注入到infrastructure的类中,彻底打破循环。
方案3:用Instance动态查找实现类
如果确实需要在domain模块中获取实现类,可借助CDI的Instance<T>动态查找,无需依赖application模块:
- 在domain模块添加Quarkus CDI API依赖(仅API,不依赖实现):
<!-- domain模块的pom.xml --> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-arc</artifactId> <scope>provided</scope> </dependency>
- 在domain模块的类中使用
Instance<T>动态获取实现:
// domain模块中的类 @Inject Instance<BusinessService> serviceInstance; public void useService() { // 通过Qualifier筛选出application层的实现 BusinessService service = serviceInstance.select(AppImpl.class).get(); service.execute(); }
运行时只要application模块被加载,CDI容器就能找到对应的实现类,编译时domain无需依赖application,避免循环。
内容的提问来源于stack exchange,提问作者Clément Gibert

