IoC 和 DI 是众多框架的通用设计思想——Spring、Google Guice、Java CDI、.NET Core 都用它,只是 Spring 应用得最广。平时天天用 @Autowired、@Bean,但"控制反转"到底反转了什么、IoC 和 DI 是不是一回事、三种注入方式怎么选——这些基础概念值得系统梳理一遍。这篇把 IoC 从头讲清楚,是理解《AOP 面向切面编程》和《Spring Boot 自动配置原理》的前提。
一、解决什么问题:对象之间的耦合
先看不用 IoC 的写法:
public class UserService {
// 写死了具体实现,耦合死了
private UserDao userDao = new UserDaoImpl();
public User getUser(int id) {
return userDao.selectById(id);
}
}UserService 直接 new UserDaoImpl(),带来了三个问题:
- 强耦合:想换成
UserDaoMyBatisImpl,就得改UserService的代码 - 难测试:单测
UserService时,没法用一个"假的 UserDao"替换掉真的 - 重复创建:每个用到 UserDao 的地方都要自己
new,创建逻辑散落各处
核心矛盾一句话:对象自己掌控了"创建依赖对象"的权力,导致它和具体实现绑死。
二、IoC 是什么:把"创建权"交出去
IoC(Inversion of Control,控制反转)是一种思想:把"创建对象、管理依赖"的控制权,从对象自己手里,反转给一个外部容器。
public class UserService {
private UserDao userDao;
// 依赖不再自己 new,而是由外部(容器)注入进来
public UserService(UserDao userDao) {
this.userDao = userDao;
}
}现在 UserService 只依赖 UserDao 接口,不关心具体实现。具体注入哪个实现,由容器决定——想换实现,改容器配置就行,UserService 一行不用动。
三、IoC 和 DI 是不是一回事
不是一回事,是"思想"和"实现"的关系:
| 概念 | 定位 | 说明 |
|---|---|---|
| IoC(控制反转) | 思想/原则 | "创建对象的控制权从自己反转到容器" |
| DI(依赖注入) | 实现手段 | 容器把依赖"注入"进对象(构造器/setter) |
IoC 是目标,DI 是实现这个目标最主流的方式。除了 DI,还有一种叫 Service Locator(服务定位器),但因为 DI 更简单、依赖更明确,所以 Spring 主推 DI。
一句话:IoC 是"反转控制权"这个思想,DI 是"怎么把依赖送进去"这个动作。
四、三种注入方式怎么选
// ① 构造器注入(推荐):依赖不可变、必填、便于测试
@Service
public class UserService {
private final UserDao userDao; // final,一旦注入不可改
public UserService(UserDao userDao) { // Spring 自动按类型注入
this.userDao = userDao;
}
}
// ② Setter 注入:适合可选依赖
@Service
public class UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) { this.userDao = userDao; }
}
// ③ 字段注入(不推荐):@Autowired 直接打在字段上
@Service
public class UserService {
@Autowired
private UserDao userDao; // 隐藏依赖、不能 final、难测试
}| 方式 | 优点 | 缺点 | 场景 |
|---|---|---|---|
| 构造器注入 | 依赖不可变、必填清晰、易测试 | 参数多时构造器变长 | 首选 |
| Setter 注入 | 可选依赖灵活 | 可能漏注入、可变 | 可选依赖 |
| 字段注入 | 代码最简洁 | 隐藏依赖、难单测 | 不推荐(老代码常见) |
五、Spring 容器:IoC 思想的落地
前面几节说的"把创建权交给外部容器",那个"容器"在 Spring 里是真实存在的类,叫 Spring 容器(IoC 容器)。它是 IoC 思想的落地实现——一个负责"创建对象、管理对象、注入依赖"的东西,可以理解成对象的管家。
先认识一个词:Bean。容器里管理的对象,统称 Bean(豆子,比喻容器是个装豆子的罐子)。前面代码里 @Service、@Component 标注的类,容器启动时会创建它的实例、放进容器管理,这个实例就是一个 Bean。
Spring 容器有两个层次,日常用增强版:
| 容器 | 定位 | 特点 |
|---|---|---|
| BeanFactory | 最底层的接口 | 懒加载:用到哪个才创建哪个,一般不直接用 |
| ApplicationContext | 继承 BeanFactory 的增强版 | 启动时提前创建好所有 Bean,还带事件、国际化等,日常都用它 |
// 从容器拿对象,而不是自己 new
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = ctx.getBean(UserService.class); // 容器已注入好依赖启动时,容器扫描配置、创建所有 Bean、把依赖一个个注入进去——前面说的"创建 + 注入 + 管理"三步,就在这一步自动完成,这就是"反转"的落地。
一句话串起来:IoC 是思想(把创建权交出去),Spring 容器是实现这个思想的"对象管家",DI 是它注入依赖的方式。
小结
- IoC 解决"对象自己 new 导致强耦合"的问题,把创建权反转给容器
- IoC 是思想,DI 是实现(构造器/setter 注入依赖)
- 注入方式首选构造器注入(不可变、必填清晰、易测试),字段注入不推荐
- Spring 容器 = ApplicationContext,从容器拿对象而不是自己 new
IoC 管"对象怎么来、依赖怎么注入",AOP 管"横切逻辑怎么统一织入"——接着看《AOP 面向切面编程》,它解决事务、日志这类散落在各处的代码。
