原文来自于:zha-ge.cn/java/123
对象居然不用自己创建?Spring 依赖注入机制的真相惊呆了!
刚学 Java 的时候,我写的每一行代码都充满了 new
:
ini
OrderService orderService = new OrderService();
UserRepository userRepository = new UserRepository();
我以为这就是"面向对象"的正确打开方式,直到我第一次接触 Spring,发现我根本不用 new
,对象就神奇地出现了!
那一刻我直接惊呆了:
"我啥时候 new 的?它怎么就有了?我都没创建对象啊!"
其实,这就是 Spring 最核心、也最"神奇"的能力之一------依赖注入(Dependency Injection, DI) 。如果说 IoC(控制反转)是 Spring 的灵魂,那 DI 就是它的手脚。
从"自己 new"到"别人塞给你":控制权反转了
还记得传统写法吗?对象的创建和依赖关系我们自己来搞定:
java
public class OrderService {
private UserRepository userRepository = new UserRepository();
}
这种写法的问题是:类与类强耦合 ,OrderService
一辈子只能用这个版本的 UserRepository
,扩展和测试都不灵活。
而在 Spring 世界里,写法变成了这样:
kotlin
@Service
public class OrderService {
@Autowired
private UserRepository userRepository;
}
没了 new
,却依然能用。为什么?
因为控制权已经反转(IoC) :对象的创建不再由你自己掌控,而是交给了 Spring 容器,它会在恰当的时候,把你需要的对象"塞进来" ------这就是依赖注入。
IoC 是理念,DI 是落地方式
经常有人搞混 IoC 和 DI 的关系:
- IoC(Inversion of Control) 是一个设计思想,强调"谁来控制对象的创建和依赖"。
- DI(Dependency Injection) 是 IoC 的实现方式,通过"注入"把依赖交给容器完成。
换句话说:
👉 IoC 是"让别人来帮你造对象"的哲学;
👉 DI 是"别人怎么给你塞对象"的技术手段。
三种常见的依赖注入方式
Spring 支持多种注入方式,不同写法背后原理一样,但场景略有不同👇
1. 构造器注入(推荐 ✅)
kotlin
@Service
public class OrderService {
private final UserRepository userRepository;
@Autowired
public OrderService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
📌 优点:
- 对象创建时依赖就确定了,不可能出现"空对象"
- 更利于单元测试和不可变设计
2. Setter 注入(适合可选依赖)
typescript
@Service
public class OrderService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
📌 优点:依赖可以动态修改,适合可选注入
📌 缺点:对象创建后依赖才注入,存在被"忘记"的风险
3. 字段注入(最常见但不推荐 ❌)
kotlin
@Service
public class OrderService {
@Autowired
private UserRepository userRepository;
}
📌 优点:写法最简洁
📌 缺点:可测试性差,不利于解耦,违反了"依赖显式化"的设计原则
DI 背后的"魔法":反射 + Bean 容器
Spring 到底是怎么把对象塞进去的?其实原理很朴素👇:
- Bean 定义加载:Spring 启动时会扫描注解、解析配置,记录有哪些 Bean。
- Bean 实例化 :通过反射
Class.newInstance()
创建对象实例。 - 依赖解析 :发现字段或构造器参数上有
@Autowired
,就去容器里找匹配的 Bean。 - 依赖注入 :用反射(
Field.set()
/ 调用构造器)把 Bean 塞进目标类。 - 后置处理 & 生命周期回调:Bean 初始化完成后再进行 AOP 增强等。
你写的 @Autowired
只是一个声明,幕后 Spring 已经在拼命用反射和容器做"搬运工"了。
踩坑瞬间:DI 失效的 3 个经典原因
-
自己 new 的对象注不进去
你手动 new 出来的对象不在 Spring 容器里,容器自然不会管它。
✅ 解决:交给 Spring 管理,用
@Component
或在配置类中用@Bean
注册。 -
Bean 名称或类型冲突
如果有多个相同类型的 Bean,Spring 不知道注入谁,会抛出异常。
✅ 解决:用
@Qualifier("beanName")
明确指定。 -
循环依赖
A 依赖 B,B 又依赖 A,容器可能注不进去。
✅ 解决:使用 Setter 注入或
@Lazy
延迟加载来打破死循环。
面试官杀手锏回答
问:Spring 的依赖注入是怎么实现的?
建议你这样答👇:
- Spring 容器负责对象的创建、管理和依赖注入,实现了控制反转(IoC)。
- 依赖注入有三种方式:构造器注入、Setter 注入和字段注入。
- Spring 启动时会扫描 Bean、创建实例,并通过反射将依赖注入到目标 Bean 中。
@Autowired
只是声明,真正注入依赖的是容器的 BeanFactory / ApplicationContext。- DI 的核心价值是解耦,让对象不再依赖具体实现,易于扩展和测试。
写在最后:从"自己造"到"别人给",是架构的分水岭
当你开始习惯用依赖注入的时候,你就从"写代码"跨进了"设计系统"的门槛。
DI 带来的不是偷懒,而是彻底的解耦:
- 业务逻辑和对象创建分离
- 扩展和替换几乎不需要改动代码
- 单元测试不再依赖真实实现
一句话记住:
依赖注入不是省事的语法糖,而是让代码变得可维护、可测试、可扩展的根本武器。