Spring Boot依赖注入本质是IOC容器在Bean初始化阶段,按类型或名称通过反射完成“声明—查找—装配”的全过程;构造器注入最安全,字段注入已不推荐,注入失败多因扫描范围、类型不匹配、循环依赖或对象未被容器管理。

直接说核心:Spring Boot 的依赖注入(DI)不是“自动塞值”这么简单,而是容器在创建 Bean 时,根据类型或名称,用反射把其他 Bean “找出来、配进去”的全过程。它背后靠的是 IOC 容器的控制权移交 + 注解驱动的元数据解析 + 后置处理器的精细装配。
依赖注入本质是“谁来管对象之间的关系”
传统写法里,UserService 要用 UserRepository,就得自己 new 出来——这叫“正向控制”,耦合死、难测试、换实现要改代码。DI 把这个控制权交给 Spring 容器:你只声明“我需要一个 UserRepository”,容器负责找到它、实例化它、再塞进 UserService 里。这个“声明—查找—装配”的链条,就是 DI 的真实动作。
关键点在于:注入不是发生在运行时方法调用那一刻,而是在 Bean 初始化阶段完成的。比如 @Autowired 标在字段上,实际是在 AbstractAutowireCapableBeanFactory.applyPropertyValues 这一步,通过 AutowiredAnnotationBeanPostProcessor 扫描并完成赋值。
三种注入方式,底层逻辑其实不同
- 构造器注入:最严格也最推荐。容器在调用构造方法前,先准备好所有参数 Bean;一旦对象创建成功,依赖就不可变。Spring 4.3+ 单构造器可省略 @Autowired,靠参数类型自动匹配。
- Setter 注入:容器先调用无参构造器创建对象,再通过反射调用 setXxx 方法赋值。适合可选依赖或后期重设场景,但不如构造器安全。
- 字段注入(@Autowired 在属性上):不走构造或 setter,而是直接用反射操作 private 字段。虽然写起来最简,但破坏封装、无法做 final 保护、单元测试难 mock,官方已不推荐。
容器怎么“找得到”你要的那个 Bean?
不是靠扫描类名,而是靠类型(byType)优先匹配:
- 默认按接口或父类类型去容器里查——比如@Autowired UserRepository,容器会找所有实现了 UserRepository 的 Bean。
- 如果多个候选(比如有两个实现类),就按变量名(byName)进一步筛选:private UserRepository mysqlRepo; 就会优先找名为 mysqlRepo 的 Bean。
- @Qualifier 或 @Primary 是人工干预匹配逻辑的手段,用于打破歧义。
注意:@Resource 是 JDK 标准注解,行为相反——先 byName,失败再 byType,和 @Autowired 不是一个体系。
为什么有时候注入失败?常见卡点在这儿
- Bean 没被扫描到:比如启动类在 com.example.app,而 Service 在 com.example.service(同级包),@ComponentScan 默认不扫兄弟包,得手动指定 basePackages。
- 类型不匹配:注入点声明的是接口,但容器里只有具体实现类且没加 @Service/@Component,或者实现类加了 @ConditionalOnMissingBean 等条件注解导致没注册。
- 循环依赖:A 依赖 B,B 又依赖 A。Spring 默认用三级缓存解决单例的 setter 循环,但构造器循环无法解决,2.6+ 版本直接报错禁止。
- 非容器管理的对象:比如 new 出来的工具类、Filter 中的 this,不在 Spring 上下文里,@Autowired 失效——这时得主动从 ApplicationContext.getBean() 拿。











