依赖注入(DI,Dependency Injection)是 Spring 框架最核心、最基础 的特性,也是 SpringBoot 自动配置、Bean 管理、AOP、事务、微服务组件生效的底层基石。绝大多数开发者仅会使用**
@Autowired、@Resource** 注解完成注入,但对 DI 的底层原理、注入方式、优先级、生命周期、循环依赖解决方案、适用场景认知片面,极易出现空指针、注入失败、循环报错、组件覆盖等线上问题。本文基于 SpringBoot 2.x/3.x 通用规范,通过多维度表格全景对比+底层源码流程+实战场景+误区避坑,全方位梳理依赖注入核心知识点,构建完整的 DI 知识体系,适配面试、开发、架构落地全场景。
一、依赖注入核心概念与本质
1.1 核心定义
依赖 :一个类需要调用另一个类的方法/属性才能完成业务功能,二者形成依赖关系。传统开发中,类主动通过 new 创建依赖对象,耦合度极高。
依赖注入(DI) :将对象的创建、管理、依赖赋值全权交给 Spring IOC 容器 ,开发者无需手动 new 对象,容器自动实例化 Bean、组装依赖关系,实现控制反转(IOC),彻底解耦业务代码。
1.2 传统开发 vs Spring DI 开发对比表
| 对比维度 | 传统手动 new 开发 | SpringBoot DI 依赖注入 |
|---|---|---|
| 对象创建方式 | 开发者手动 new 实例 | Spring IOC 容器自动实例化 |
| 依赖赋值方式 | 代码硬编码赋值,主动依赖 | 容器自动注入,被动接收依赖 |
| 代码耦合度 | 高,代码强依赖具体实现类 | 极低,依赖抽象接口,面向接口编程 |
| 对象复用性 | 每次 new 都是新对象,资源浪费 | 默认单例 Bean,全局复用,节省资源 |
| 维护成本 | 依赖变更需改全部代码,扩展性差 | 无需改业务代码,通过配置/注解切换实现 |
| 事务、AOP 能力 | 无,无法使用 Spring 增强能力 | 支持,所有 Spring 特性基于 DI 生效 |
1.3 IOC 与 DI 核心关系
IOC(控制反转)是思想,DI(依赖注入)是实现:
控制反转:将对象控制权从开发者交给容器,反转资源管控权限;
依赖注入:容器实现 IOC 思想的具体手段,是 Spring 解耦的核心落地方式。
二、SpringBoot 三种核心注入方式全景对比
Spring 官方定义三种标准依赖注入方式:构造器注入、Setter 注入、字段注入,三种方式的优先级、底层逻辑、适用场景、优缺点差异极大。
2.1 三种注入方式全维度对比表
| 对比维度 | 构造器注入(官方推荐) | Setter 方法注入 | 字段注入(@Autowired 直接注入) |
|---|---|---|---|
| 实现方式 | 通过类构造方法传入依赖参数 | 通过 Java Bean Setter 方法赋值 | 类属性直接添加 @Autowired/@Resource |
| 注入时机 | Bean 实例化阶段同步注入 | Bean 实例化完成后,属性赋值阶段注入 | Bean 实例化后,后置处理器统一注入 |
| 必填依赖支持 | 天然支持,强制注入,避免空指针 | 可选依赖,可动态修改依赖 | 默认必填,需 required=false 设为可选 |
| 循环依赖解决 | 无法解决,直接抛出异常 | 可以解决单例循环依赖 | 可以解决单例循环依赖 |
| 代码可测试性 | 极高,无 Spring 环境可手动 new 传参测试 | 较高,可手动调用 Setter 赋值 | 极差,强依赖 Spring 容器,无法独立单元测试 |
| Spring 官方态度 | 强制推荐,Spring4.3+ 首选 | 兼容保留,适用于可选依赖 | 不推荐,代码耦合隐蔽,隐患多 |
| 适用场景 | 所有必填核心依赖(Service、Dao 核心组件) | 非必须、可动态切换的可选依赖 | 快速开发、简单项目(不用于正式规范开发) |
2.2 三种注入方式代码示例
1. 构造器注入(标准推荐)
SpringBoot 4.3+ 单构造器可省略 @Autowired 注解
@Service
public class OrderService {
// 单构造器,自动注入
private final UserService userService;
public OrderService(UserService userService) {
this.userService = userService;
}
}
2. Setter 注入
@Service
public class OrderService {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
3. 字段注入(不推荐)
@Service
public class OrderService {
@Autowired
private UserService userService;
}
三、主流注入注解对比(@Autowired、@Resource、@Qualifier)
开发中最常用的三个注入注解,底层规则、匹配逻辑、来源、容错性完全不同,是 DI 核心高频考点。
3.1 注解全维度对比表
| 对比维度 | @Autowired | @Resource | @Qualifier |
|---|---|---|---|
| 注解来源 | Spring 自定义注解 | JSR-250 Java 原生标准注解 | Spring 辅助注解 |
| 默认匹配规则 | 默认按类型(byType)注入 | 默认按名称(byName)注入,名称匹配失败再按类型 | 精准指定 Bean 名称,解决多实例冲突 |
| 必填属性 | required=true(默认必填),可设为 false | 默认必须注入,无 required 配置 | 无注入属性,仅作匹配修饰 |
| 多 Bean 冲突场景 | 同类型多 Bean 直接报错,需配合 @Qualifier | 按变量名匹配 Bean,可规避部分冲突 | 强制指定注入 Bean 名称,彻底解决冲突 |
| 底层依赖 | Spring IOC 专属,脱离 Spring 失效 | Java 标准,兼容所有 DI 容器 | 配合 @Autowired 使用,不可单独使用 |
| 适用场景 | 绝大多数 SpringBoot 业务注入 | 需要跨容器兼容、按名称精准注入场景 | 接口多实现类、同类型多 Bean 场景 |
3.2 多实现类注入冲突解决方案
当一个接口有多个实现类时,@Autowired 按类型注入会抛出 NoUniqueBeanDefinitionException 唯一 Bean 冲突异常,两种标准解决方案:
使用 @Qualifier + @Autowired 指定 Bean 名称;
使用 @Resource,变量名与 Bean 名称一致,自动匹配。
四、Spring DI 底层执行流程(核心原理)
依赖注入发生在 Spring IOC 容器启动、Bean 初始化阶段,完整流程分为 扫描定位、实例化、属性填充、初始化、完成注入 五个核心步骤。
4.1 DI 完整执行流程表
| 执行阶段 | 核心操作 | 关键底层组件 |
|---|---|---|
| 1. 资源扫描 | SpringBoot 启动时通过包扫描,识别所有带 @Service、@Component、@Repository 的组件,生成 Bean 定义信息(BeanDefinition) | BeanDefinitionScanner |
| 2. Bean 实例化 | 容器根据 BeanDefinition,通过反射调用构造器创建 Bean 空对象(此时属性全部为空) | AbstractAutowireCapableBeanFactory |
| 3. 依赖注入(核心) | 调用 Bean 后置处理器,解析 @Autowired/@Resource 注解,从容器中匹配依赖 Bean,完成属性赋值 | AutowiredAnnotationBeanPostProcessor |
| 4. 初始化方法执行 | 执行 @PostConstruct 标注方法、InitializingBean 初始化方法,完成业务初始化 | Bean 初始化处理器 |
| 5. 完成注入 | Bean 完全就绪,存入单例池,全局可直接使用 | Spring 单例缓存池 |
五、Bean 作用域与 DI 注入关联关系
依赖注入的效果、对象复用、循环依赖问题,完全依赖 Bean 的作用域,不同作用域注入逻辑差异巨大。
5.1 核心作用域 DI 特性对比表
| 作用域 | 生命周期 | DI 注入特性 | 循环依赖支持 |
|---|---|---|---|
| singleton(默认单例) | 容器启动创建,全局唯一 | 全局注入同一个 Bean 对象,性能最高 | 支持 setter/字段注入,不支持构造器 |
| prototype(多例) | 每次注入创建新对象 | 每次获取都是全新实例,无对象复用 | 完全不支持,直接报错 |
| request | 单次 HTTP 请求有效 | 请求内统一对象,跨请求重新注入 | 不支持 |
| session | 单次会话有效 | 同会话复用对象,会话过期销毁 | 不支持 |
六、DI 核心难点:循环依赖全景解析
循环依赖是 DI 开发中最常见的报错问题,核心场景:A 依赖 B,B 依赖 A。Spring 仅能解决单例模式下的字段、Setter 循环依赖。
6.1 各类注入循环依赖支持情况表
| 注入方式 | 单例 Bean | 多例 Bean | 报错原因 |
|---|---|---|---|
| 字段注入 | ✅ 支持解决 | ❌ 不支持 | 多例无缓存,无法提前暴露对象 |
| Setter 注入 | ✅ 支持解决 | ❌ 不支持 | 多例无缓存机制 |
| 构造器注入 | ❌ 不支持 | ❌ 不支持 | 实例化必须传参,无法提前暴露空对象 |
6.2 Spring 循环依赖解决核心原理
依靠 Spring 三级缓存机制:
一级缓存:存放完全初始化好的单例 Bean;
二级缓存:存放已实例化、未完成注入的半成品 Bean;
三级缓存:存放 Bean 工厂,用于提前暴露对象引用。
字段/Setter 注入可先创建空对象存入缓存,再完成依赖赋值;构造器注入必须初始化时传入完整依赖,无法借助缓存,因此无法解决循环依赖。
七、DI 常见报错与解决方案对照表
7.1 高频异常场景全景表
| 异常信息 | 报错原因 | 解决方案 |
|---|---|---|
| NoSuchBeanDefinitionException | 容器中无对应 Bean,未托管、包扫描不到、注解缺失 | 添加 @Service/@Component、检查包扫描范围、确认 Bean 已实例化 |
| NoUniqueBeanDefinitionException | 接口多实现类,byType 注入匹配多个 Bean | 使用 @Qualifier 指定名称 / @Resource 按名称注入 |
| BeanCurrentlyInCreationException | 构造器循环依赖,无法解决 | 改为字段/Setter 注入,或重构代码解除循环依赖 |
| NullPointerException 注入空指针 | 手动 new 对象、静态方法调用、未交给容器管理 | 禁止手动 new,统一使用容器注入,非静态调用 Bean |
八、DI 企业级最佳实践与规范
8.1 开发规范汇总表
| 开发场景 | 推荐规范 | 禁止写法 |
|---|---|---|
| 普通业务组件注入 | 构造器注入 + final 修饰,保证不可变、必填 | 禁止长期使用字段注入 |
| 可选依赖注入 | 使用 Setter 注入,灵活配置依赖 | 必填依赖使用 Setter 注入 |
| 多实现类注入 | @Autowired + @Qualifier 精准匹配 | 依赖变量名侥幸匹配,代码不健壮 |
| 工具类/静态方法 | 通过 Spring 上下文获取 Bean,非静态注入 | 静态属性直接 @Autowired 注入(永久空指针) |
| 循环依赖处理 | 优先代码重构,彻底解耦 | 依赖缓存机制强行规避,遗留隐患 |
九、全文核心总结
1、DI 本质 :依赖注入是 Spring IOC 思想的落地实现,核心是容器管控对象、自动组装依赖,彻底解耦代码;
2、注入优先级:构造器注入(官方首选)> Setter 注入(可选依赖)> 字段注入(仅快速开发);
3、注解核心差异:@Autowired 默认按类型,@Resource 默认按名称,@Qualifier 精准解决多 Bean 冲突;
4、循环依赖边界:仅单例模式下字段、Setter 注入可解决,构造器、多例模式完全无法解决;
5、落地原则 :业务开发统一使用构造器注入+final,拒绝字段注入滥用,规避空指针、循环依赖、注入冲突问题。