# Spring 中的 DI 是什么?

6 min read
Table of Contents

Spring 中的 DI 是什么?

Spring 中的 DI(依赖注入)面试标准答案

【核心定义】

DI(Dependency Injection,依赖注入)是 Spring 框架实现 IoC(控制反转) 的核心机制,其本质是将对象依赖关系的创建和管理从程序内部转移到外部容器,通过容器在运行时动态地将依赖对象注入到目标对象中。

【关键要点】

  1. 控制反转(IoC)的实现方式
    DI 是 IoC 的具体技术实现。传统编程中,对象主动创建和管理其依赖;在 DI 模式下,容器负责创建并注入依赖,控制权发生反转。

  2. 三种主要注入方式

    • 构造器注入:通过构造函数参数注入依赖,强制依赖完整,利于不可变对象和线程安全。
    • Setter 注入:通过 setter 方法注入,灵活性高,支持可选依赖。
    • 字段注入:通过 @Autowired 直接注入字段,代码简洁但隐藏依赖关系,不推荐用于强制依赖。
  3. 依赖查找 vs 依赖注入
    DI 是容器主动推送依赖到对象;依赖查找(如 BeanFactory.getBean())是对象主动向容器索取依赖。Spring 主要倡导 DI,更符合松耦合原则。

  4. @Autowired 注解的工作机制
    默认按类型匹配,配合 @Qualifier 可按名称指定。注入过程发生在 Bean 生命周期的属性填充阶段,由 AutowiredAnnotationBeanPostProcessor 处理。

【深度推导/细节】

核心矛盾:循环依赖的解决

Spring 通过 三级缓存 机制解决构造器注入无法处理的循环依赖问题(仅限单例 Bean 且非构造器注入):

  • 一级缓存(singletonObjects):存放完全初始化好的 Bean。
  • 二级缓存(earlySingletonObjects):存放早期暴露的 Bean(已实例化但未填充属性)。
  • 三级缓存(singletonFactories):存放 Bean 工厂,用于生成早期引用。

解决流程(以 A 依赖 B,B 依赖 A 为例):

  1. 创建 A 实例,放入三级缓存(工厂可生成 A 的早期引用)。
  2. 填充 A 的属性时发现需要 B,触发 B 的创建。
  3. 创建 B 实例,放入三级缓存。
  4. 填充 B 的属性时发现需要 A,从三级缓存通过工厂获取 A 的早期引用(此时 A 未初始化完),注入给 B。
  5. B 完成初始化,移入一级缓存。
  6. A 获得完整的 B 实例,完成初始化,移入一级缓存。

关键限制:构造器注入的循环依赖无法解决,因为实例化阶段就需要完整依赖,此时 Bean 未放入缓存。

【关联/对比】

对比维度Spring DI手动依赖管理
控制权容器控制对象自身控制
耦合度低(面向接口)高(直接实例化)
可测试性高(易替换 Mock)低(硬编码依赖)
配置方式注解/XML/Java Config代码硬编码

与 IoC 容器的关系:
DI 是手段,IoC 是目标。Spring 容器(ApplicationContext)作为 IoC 容器,通过 DI 管理 Bean 的生命周期和依赖关系。

【面试官追问】

  1. Spring 如何解决字段注入的循环依赖?与构造器注入有何不同?
    (如上所述,字段注入可利用三级缓存;构造器注入在实例化前就需要完整依赖,无法解决。)

  2. @Autowired 和 @Resource 的区别?

    • @Autowired 是 Spring 注解,默认按类型匹配,支持 required=false。
    • @Resource 是 JSR-250 注解,默认按名称匹配,名称找不到则回退到类型。
  3. 为什么推荐使用构造器注入?

    • 保证依赖不可变(final 字段)。
    • 保证依赖不为 null。
    • 避免循环依赖问题(设计上暴露问题)。
    • 便于单元测试。
  4. Spring 中有哪些注入的“边界情况”或限制?

    • 静态字段无法直接注入(需通过 setter 方法配合 @PostConstruct)。
    • 同一类型的多个 Bean 需配合 @Qualifier 指定。
    • 原型 Bean 的循环依赖无法解决(每次获取新实例,缓存机制无效)。
  5. 从 Spring 4.3 开始,如果类只有一个构造器,@Autowired 可以省略,为什么?
    Spring 认为单一构造器是明确的依赖声明,自动将其视为注入点,简化配置。

【版本差异】

  • Spring 2.5:引入 @Autowired 注解支持。
  • Spring 3.0:强化 Java Config,@Configuration 与 @Bean 提供纯 Java 配置方式。
  • Spring 4.3:单构造器隐式自动注入,推荐构造器注入。
  • Spring 5.x:对 Kotlin 原生支持,支持空安全注入。

【最佳实践总结】

  • 强制依赖使用构造器注入,可选依赖使用 Setter 注入。
  • 避免字段注入,尤其是核心业务 Bean。
  • 使用 @Qualifier 或 @Primary 解决同一类型多 Bean 的歧义。
  • 通过 @Profile 或条件化 Bean(@Conditional)实现环境特定注入。
My avatar

文章看完了,不打算去别的篇章转转吗?👀 感谢你的耐心阅读!如果你有问题想探讨,或者发现了文中的 Bug,请务必通过页脚的链接联系我


More Posts

Comments