Spring @DependsOn跨模块依赖配置类报错:找不到dbMigration Bean
我们的项目包含一个commons模块,其中有一个用于执行数据库自定义代码的配置类:
@Configuration public class DbMigration { [...] @PostConstruct public void doStuff() { // 必须先执行 [...] } }
主项目中有一个服务类,其@PostConstruct代码块需要DbMigration先执行完成:
@Service @DependsOn("dbMigration") public class ElasticSearchMigrationService { [...] @PostConstruct public void doStuff() { // 必须后执行 [...] } }
当DbMigration与ElasticSearchMigrationService在同一模块时该方案可正常运行,但将DbMigration移至commons模块后,Spring启动失败并抛出异常:
[...] Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'dbMigration' available at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:872) at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1344) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:309) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322) ... 57 more
已找到一种不满意的解决方案:在DbMigration类内部创建一个DbMigration类型的@Bean。同时无法将ElasticSearchMigrationService移至commons模块,因其包含仅本应用所需的特定代码。需要其他可行解决方案,或指出忽略的关键点。
1. 确保commons模块的配置类被Spring扫描到
Spring默认只会扫描主项目启动类所在包及其子包,若commons模块的DbMigration不在这个扫描范围内,需要手动指定:
- 在主项目启动类上添加
@ComponentScan(basePackages = {"你的主项目根包", "commons模块的根包路径"}) - 或者在commons模块中创建一个入口配置类,用
@Import(DbMigration.class)导入目标类,再让主项目扫描这个入口配置类
2. 显式指定DbMigration的Bean名称
Spring默认用类名首字母小写作为Bean名称(比如DbMigration对应dbMigration),但如果commons模块的配置类被代理(如CGLIB代理),可能导致Bean名称变化。可以直接在@Configuration上指定名称:
@Configuration("dbMigration") public class DbMigration { // ... 原有代码 }
3. 用类型依赖替代名称依赖
放弃@DependsOn的名称指定方式,改用构造注入让Spring自动保证初始化顺序:
@Service public class ElasticSearchMigrationService { private final DbMigration dbMigration; // 构造注入时,Spring会先初始化DbMigration再创建当前Bean,自然保证其@PostConstruct先执行 public ElasticSearchMigrationService(DbMigration dbMigration) { this.dbMigration = dbMigration; } @PostConstruct public void doStuff() { // ... 原有业务逻辑 } }
这种方式不需要@DependsOn,依赖关系更直观且不易出错。
4. 监听Spring上下文刷新事件
如果构造注入不适用,可以让ElasticSearchMigrationService监听ContextRefreshedEvent,此时上下文已完成所有Bean的初始化,DbMigration的@PostConstruct肯定已经执行:
@Service public class ElasticSearchMigrationService implements ApplicationListener<ContextRefreshedEvent> { @Autowired private DbMigration dbMigration; private boolean executed = false; @Override public void onApplicationEvent(ContextRefreshedEvent event) { // 加标记避免多次执行(Spring可能触发多次上下文刷新) if (!executed) { doStuff(); executed = true; } } public void doStuff() { // ... 原有业务逻辑 } }
内容的提问来源于stack exchange,提问作者tengoku

