Spring 中的IoC(控制反转)和 DI(依赖注入)
在 Spring 框架中,IoC(控制反转) 和 DI(依赖注入) 是其最核心的基石。理解了它们,就等于掌握了 Spring 的灵魂。
为了让你通俗易懂地理解,我们将从概念、关系、实现方式以及优势四个方面进行拆解。
一、 什么是 IoC(Inversion of Control - 控制反转)?
IoC 是一种设计思想,而不是一种具体的技术。
传统开发模式(正转):
假设对象 A 需要使用对象 B 的功能,通常会在对象 A 的代码里直接new一个对象 B。
控制权在哪? 在对象 A 手里。A 负责决定什么时候创建 B,怎么创建 B。
缺点: A 和 B 强耦合(绑死在一起了),如果 B 的构造方式变了,A 的代码也要跟着改。IoC 开发模式(反转):
对象 A 需要对象 B,但 A 不再自己去new对象 B 了,而是把创建和管理对象 B 的权力交给了 Spring 容器。Spring 容器会在合适的时机,主动把对象 B 交给(注入给)对象 A。
控制权在哪? 反转给了第三方(Spring 容器)。
“反转”的到底是什么? 是获取依赖对象的控制权被反转了。
🍔 通俗比喻:
传统模式(自己做饭): 你想吃汉堡,你需要自己去买面包、买肉饼、生火、煎肉、组装(自己new对象)。
IoC 模式(点外卖): 你想吃汉堡,你只需要给外卖平台(Spring 容器)下个单(声明需求),外卖平台会让商家做好,派骑手送到你手里。你不需要关心汉堡是怎么做出来的。
二、 什么是 DI(Dependency Injection - 依赖注入)?
DI 是 IoC 思想的具体实现方式。
- 依赖(Dependency): 对象 A 运行需要对象 B,我们就说 A 依赖于 B。
- 注入(Injection): Spring 容器在创建对象 A 时,自动将对象 A 所需要的对象 B 传递(注入)给它。
IoC 和 DI 的关系:
IoC 是目标和思想,DI 是手段和实现。 (就像“我要去北京”是 IoC,“坐高铁去”是 DI)。
三、 Spring 中实现 DI(依赖注入)的三种方式
在 Spring 中,把对象放进容器叫做 Bean,Spring 提供了三种主要的方式来将依赖的 Bean 注入到目标代码中:
1. 属性注入(Field Injection)- 最常见
通过在类的成员变量上直接加 @Autowired 或 @Resource 注解。
@Service
public class UserService {
// Spring 会自动找到 UserRepository 的实例并赋值给这个变量
@Autowired
private UserRepository userRepository;
public void getUser() {
userRepository.findUser();
}
}
- 优点: 代码极其简洁。
- 缺点: 隐藏了依赖关系,离开 Spring 容器后难以进行单元测试(容易报空指针)。
2. 构造器注入(Constructor Injection)- Spring 官方强推
通过类的构造方法来传入依赖对象。如果类只有一个构造方法,Spring 会隐式地自动注入(@Autowired 可以省略)。
@Service
public class UserService {
private final UserRepository userRepository;
// 依赖通过构造方法传入
@Autowired // 如果只有一个构造器,这个注解可省略
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
- 优点: 保证依赖不可变(可以加
final),确保对象被实例化时依赖已经准备好,非常方便脱离 Spring 进行单元测试(直接 new 并传入 mock 对象即可)。
3. Setter 方法注入(Setter Injection)
通过提供 Setter 方法,并在方法上加 @Autowired。
@Service
public class UserService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
- 优点: 适合注入可选的依赖(允许依赖为空或后续更改)。
四、 Spring 是如何知道该创建/注入哪些对象的?
Spring 容器需要知道你的“点餐需求”,这就需要配置。现代 Spring (Spring Boot) 主要通过注解来实现:
把对象交给 Spring 容器管理(声明 Bean):
@Component:通用的组件。@Service:用于业务逻辑层(Service 层)。@Repository:用于数据访问层(DAO 层)。@Controller:用于控制层(接收 HTTP 请求)。@Bean:通常写在@Configuration类里,用于将第三方库的对象放入容器。
从 Spring 容器取出对象(注入依赖):
@Autowired:Spring 提供的,默认按类型(Type)匹配。@Resource:JDK 提供的,默认按名称(Name)匹配。
五、 为什么非要用 IoC 和 DI?(核心优势)
- 极大地降低了代码耦合度(解耦):
对象之间不再有硬编码的创建关系。如果UserRepository的实现从 MySQL 换成了 Oracle,UserService的代码一行都不需要改,只需要在配置或注解里调整即可。 - 提高了代码的可测试性:
在写单元测试时,你可以轻松地自己创建一个假的(Mock)依赖对象传进去,而不需要启动整个数据库或复杂的环境。 - 集中管理对象的生命周期:
Spring 容器负责对象的创建、初始化、销毁。默认情况下,Spring 中的 Bean 都是单例(Singleton)的,这极大地节省了内存,提高了性能。开发者再也不用管各种麻烦的生命周期问题了。
总结:
IoC 告诉你:“别自己去 new 对象了,让 Spring 容器帮你管吧。”
DI 告诉你:“你要什么对象,直接在参数或属性上声明一下,Spring 容器会塞给你的。”