Spring Boot 多模块集成中的循环依赖问题与解决方案

本文详解 spring boot 应用间(如 jmix 主应用集成独立批处理模块)因包扫描冲突与 bean 创建顺序不当引发的 `beancurrentlyincreationexception` 循环引用错误,并提供基于 `@lazy`、精准包扫描、配置分离等可落地的工程化解决策略。

在将独立的 Spring Boot 批处理模块(如 com.publicismedia.uniquebatchjava)集成至 Jmix 主应用时,常见错误 Requested bean is currently in creation: Is there an unresolvable circular reference? 并非源于 Maven 依赖本身,而是由 Spring 容器在初始化阶段因隐式依赖链闭环导致的 Bean 创建死锁。从堆栈可见关键路径:
OAuth2AuthorizationServerConfiguration → tokenStore → stellantisroiApplication → batchExecuter → batchConfigRepository → entityManager → entityManagerFactory → jmix_Liquibase → dataSource
而 dataSource 又被 jmix_Liquibase 和 JPA 的 entityManagerFactory 同时依赖——若二者均尝试在初始化早期强依赖 dataSource,且 dataSource 自身配置又间接依赖其他尚未就绪的 Bean(如 Liquibase 或 Security 相关组件),即构成典型的 A → B → A 型循环依赖

✅ 根本解决策略(按推荐优先级排序)

1. 精准控制自动配置与包扫描范围

避免 @SpringBootApplication(scanBasePackages = {...}) 过度扫描引发意外 Bean 注入。应严格隔离关注域:

@SpringBootApplication(
    scanBasePackages = {
        "io.jmix.example",           // Jmix 主应用包
        "com.publicismedia.uniquebatchjava.config"  // 仅扫描批处理的配置类,非全包
    }
)
@EnableJmixDataRepositories(basePackages = "io.jmix.example.repository") // 明确限定 Jmix 仓库范围
@EnableJpaRepositories(
    basePackages = "com.publicismedia.uniquebatchjava.repository",
    entityManagerFactoryRef = "uniqueBatchEntityManagerFactory", // 指向专用 EMF
    transactionManagerRef = "uniqueBatchTransactionManager"
)
public class JmixApplication {
    public static void main(String[] args) {
        SpringApplication.run(JmixApplication.class, args);
    }
}
⚠️ 关键点:绝不使用空 basePackages = {}(@EnableJmixDataRepositories(basePackages = {}) 实际无效,可能触发默认扫描),而应显式指定 Jmix 自己的包;同时为批处理模块声明独立的 EntityManagerFactory 和事务管理器,彻底解耦数据源生命周期。

2. 对高风险依赖字段添加 @Lazy

当无法重构依赖关系时,在非核心初始化路径上使用延迟加载打破循环:

@Service
public class BatchExecutor {

    // 避免在构造/字段注入阶段强依赖,改用 @Lazy + 接口注入
    @Lazy
    @Autowired
    private BatchConfigRepository batchConfigRepository; // 此 Repository 依赖 EntityManager → dataSource

    // 或更安全:通过 ObjectProvider 延迟获取
    @Autowired
    private ObjectProvider batchConfigRepositoryProvider;

    public void execute() {
        BatchConfigRepository repo = batchConfigRepositoryProvider.getObject();
        // ... 业务逻辑
    }
}

3. 分离数据源与 Liquibase 配置

确保批处理模块不共享主应用的 dataSource 和 Liquibase 配置。在 application.yml 中明确隔离:

# 主应用数据源(Jmix 默认)
spring:
  datasource:
    url: jdbc:h2:mem:jmixdb
    # ... 其他配置

# 批处理专用数据源(独立)
unique-batch:
  datasource:
    url: jdbc:postgresql://localhost:5432/batchdb
    username: batchuser
    password: batchpass

# 禁用批处理模块的 Liquibase 自动配置(避免与 jmix_Liquibase 冲突)
spring:
  autoconfigure:
    exclude:
      - io.jmix.autoconfigure.data.JmixLiquibaseAutoConfiguration
      - org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration

并在 Java Config 中定义专属 Bean:

@Configuration
public class UniqueBatchDataSourceConfig {

    @Bean
    @ConfigurationProperties("unique-batch.datasource")
    public DataSource uniqueBatchDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean uniqueBatchEntityManagerFactory(
            EntityManagerFactoryBuilder builder,
            @Qualifier("uniqueBatchDataSource") DataSource dataSource) {
        return builder
                .dataSource(dataSource)
                .packages("com.publicismedia.uniquebatchjava.entity")
                .persistenceUnit("unique-batch-pu")
                .build();
    }
}

? 总结与最佳实践

  • 循环依赖本质是设计信号:优先审视 batchExecuter → batchConfigRepository → dataSource 链路是否合理——批处理服务是否必须在应用启动时就持有 Repository?考虑改为按需初始化(@Scope("prototype") 或 ObjectProvider)。
  • 永远显式而非隐

    :禁用全局 scanBasePackages,改用 @Import 导入模块配置类,或通过 @ConditionalOnClass 控制自动配置生效条件。
  • 验证依赖图:启用 --debug 启动参数,查看 CONDITIONS EVALUATION REPORT 和 BEAN DEPENDENCIES REPORT,定位真实循环节点。
  • 模块化优于集成:长期建议将批处理改造为独立服务(HTTP API 或消息队列触发),通过契约交互,从根本上规避 Spring 上下文耦合。

遵循以上方法,即可安全、稳定地将 Spring Boot 批处理模块嵌入 Jmix 等复合框架中,既复用业务逻辑,又保障容器健康。