1. 概述

Spring 默认会管理 Bean 的生命周期,并自动安排它们的初始化顺序。
但在某些复杂场景下,我们需要手动干预这个顺序。✅

有两种常见方式可以控制 Bean 初始化顺序:

  • 实现 SmartLifecycle 接口
  • 使用 @DependsOn 注解

本文重点讲解 @DependsOn 注解的使用场景和注意事项,包括:

  • 依赖 Bean 不存在的情况
  • 出现循环依赖时的行为
  • 实际应用中的最佳实践

如果你在项目中遇到“某个 Bean 必须在另一个 Bean 之前初始化”的需求,那这篇文章能帮你避免踩坑。⚠️


2. @DependsOn 注解的作用

@DependsOn 用于显式声明一个 Bean 对其他 Bean 的依赖关系。
Spring 容器会确保:被依赖的 Bean 一定在当前 Bean 初始化之前完成创建和初始化。

举个例子: 假设有一个 FileProcessor,它依赖于 FileReaderFileWriter
我们希望 Spring 先把 FileReaderFileWriter 初始化好,再创建 FileProcessor

这时就可以用 @DependsOn 来明确指定这种顺序依赖。


3. 配置方式

配置类是一个标准的 Java Config 类,使用 @Configuration 注解标注:

@Configuration
@ComponentScan("com.example.dependson")
public class Config {
 
    @Bean
    @DependsOn({"fileReader", "fileWriter"})
    public FileProcessor fileProcessor() {
        return new FileProcessor();
    }
    
    @Bean("fileReader")
    public FileReader fileReader() {
        return new FileReader();
    }
    
    @Bean("fileWriter")
    public FileWriter fileWriter() {
        return new FileWriter();
    }   
}

其他用法

除了在 @Bean 方法上使用,也可以直接标注在组件类上:

@Component
@DependsOn({"fileReader", "fileWriter"})
public class FileProcessor {
    // ...
}

⚠️ 注意:Bean 名称是大小写敏感的,写错会导致找不到依赖 Bean!


4. 使用示例

我们定义一个 File 类,作为共享数据载体。三个 Bean 分别对它进行操作:

  • FileReader:添加 "read" 标记
  • FileWriter:添加 "write" 标记
  • FileProcessor:添加 "processed" 标记

测试目标:确保 FileProcessor 被创建时,其依赖组件已初始化完毕。

@Test
public void WhenFileProcessorIsCreated_FileTextContains_Processed() {
    FileProcessor processor = context.getBean(FileProcessor.class);
    assertTrue(processor.process().endsWith("processed"));
}

这个测试验证了处理流程的完整性。如果依赖顺序混乱,可能导致状态不一致。


4.1 缺失依赖 Bean(Missing Dependency)

如果 @DependsOn 中指定的 Bean 不存在,Spring 会在启动时抛出异常 ❌

例如:

@Bean
@DependsOn("dummyFileWriter")  // 但没有定义 dummyFileWriter Bean
public FileProcessor dummyFileProcessor() {
    return new FileProcessor();
}

此时 Spring 会抛出:

BeanCreationException
  Caused by: NoSuchBeanDefinitionException: No bean named 'dummyFileWriter' available

📌 常见原因:

  • Bean 名称拼写错误
  • 扫包路径未覆盖目标类
  • 使用了 @Profile 或条件注解导致 Bean 未加载

✅ 建议:在 CI/CD 阶段运行上下文加载测试,提前暴露这类问题。


4.2 循环依赖(Circular Dependency)

当多个 Bean 通过 @DependsOn 形成闭环时,Spring 也会报错 ❌

例如:

@Bean("processorCircular")
@DependsOn("readerCircular")
public FileProcessor processorCircular() {
    return new FileProcessor();
}

@Bean("readerCircular")
@DependsOn("processorCircular")  // 形成循环!
public FileReader readerCircular() {
    return new FileReader();
}

Spring 会检测到循环依赖并抛出:

BeanCreationException: Circular depends-on relationship between 'processorCircular' and 'readerCircular'

📌 典型循环依赖链:

BeanA → BeanB → BeanC → BeanA

✅ 解决方案:

  • 重新设计模块解耦
  • 使用 @Lazy 延迟初始化打破循环(仅适用于部分场景)
  • 改用程序化初始化逻辑(如 CommandLineRunner

⚠️ 注意:@DependsOn 不支持循环依赖,也无法通过懒加载完全规避,需谨慎设计。


5. 使用要点总结

使用 @DependsOn 时必须注意以下几点:

必须启用组件扫描(@ComponentScan)
否则 @DependsOn 在组件类上的声明可能不生效。

XML 配置中声明的 Bean 会忽略 @DependsOn 注解
如果你还在用 XML 配置 Bean,那 @DependsOn 是无效的。建议迁移到 Java Config。

支持多个依赖 Bean
可以通过数组形式指定多个前置依赖:

@DependsOn({"dbInitializer", "cacheLoader", "configService"})

可用于 @Bean 和 @Component
无论你是用配置类还是组件自动扫描,都能正常使用。

⚠️ 不要滥用
只有在真正需要控制初始化顺序时才使用。大多数情况下,Spring 的自动依赖注入已经足够。


6. 结论

@DependsOn 是一个简单粗暴但非常有效的工具,适用于以下场景:

  • 数据库连接池初始化前必须完成配置加载
  • 缓存预热必须在服务启动前完成
  • 日志系统要在其他组件之前准备好

它让 Spring 的依赖注入更可控,确保关键资源按预期顺序初始化。

📌 总结一句话:
当你需要“某个 Bean 必须在另一个之前启动”,就用 @DependsOn —— 清晰、直接、可靠。

示例代码已上传至 GitHub:https://github.com/example/spring-di-demo


原始标题:Controlling Bean Creation Order with @DependsOn