IoC(控制反转)
📚 一、基础概念
1.1 什么是 IoC?
IoC(Inversion of Control,控制反转) 是一种设计原则,它改变了对象创建和依赖关系管理的方式。
🔄 传统方式 vs IoC 方式
| 方式 | 描述 | 比喻 |
|---|---|---|
| 传统方式 | 对象自己创建和管理它所依赖的其他对象 | 你饿了自己去厨房做饭 |
| IoC 方式 | 对象不再自己创建依赖,而是由外部容器来提供 | 你去餐厅,厨师为你准备好饭菜 |
1.2 什么是 DI(依赖注入)?
DI(Dependency Injection,依赖注入) 是 IoC 的具体实现方式,通过外部将依赖对象"注入"到需要它的对象中。
🎯 依赖注入的本质
// 1. 📦 准备依赖
Database database = new Database();
// 2. 🎯 注入依赖(就是普通的构造函数调用!)
UserServiceWithIoc service = new UserServiceWithIoc(database);
// ^^^^^^^^
// 这里就是注入!
// 3. ✅ 完成注入
// 现在 service 内部的 this.database 就是我们传入的 database 对象
关键理解:依赖注入不是什么魔法,就是通过构造函数参数传递对象!
1.3 什么是 Bean?
Bean 就是由 IoC 容器管理的对象。
🫘 为什么叫 "Bean"?
- ☕ 咖啡豆的比喻:Java 的 Logo 是咖啡杯,Bean(豆子)是咖啡的原料
- 🧩 组件化思想:像豆子一样,是构成应用的基本"颗粒"
- 📦 可重用性:豆子可以种植、收获、重复使用
- 📜 历史来源:来自于 JavaBeans 规范(1996年)
Bean 的特点:
- 🏭 容器创建:由 IoC 容器负责创建
- 🔧 容器管理:生命周期由容器控制
- 🎯 自动装配:依赖关系由容器自动处理
- ♻️ 可重用:可以在多个地方使用同一个 Bean
💻 二、代码对比演示
2.1 传统方式(紧耦合)
/**
* 传统方式的用户服务
* 问题:紧耦合,自己创建依赖
*/
class UserService {
private Database database;
public UserService() {
// 自己创建数据库连接 - 这就是紧耦合
this.database = new Database();
this.database.connect();
}
public void saveUser(String username) {
database.save("用户: " + username);
}
}
问题:
- ❌ 紧耦合:UserService 直接依赖 Database 的具体实现
- ❌ 难测试:无法轻易替换为模拟对象
- ❌ 难维护:修改 Database 可能影响 UserService
2.2 IoC 方式(松耦合)
/**
* IoC 方式的用户服务
* 优点:松耦合,依赖由外部注入
* 这个类的实例就是一个 Bean!
*/
class UserServiceWithIoc {
private Database database;
// 🔑 关键:通过构造函数接收依赖
public UserServiceWithIoc(Database database) {
this.database = database; // 接收外部传入的依赖
this.database.connect();
}
public void saveUser(String username) {
database.save("用户: " + username);
}
}
优点:
- ✅ 松耦合:不直接创建依赖对象
- ✅ 易测试:可以轻松注入模拟对象
- ✅ 易维护:修改依赖不影响使用方
- ✅ 单一职责:对象专注自己的业务逻辑
🏭 三、IoC 容器实现
3.1 简单容器实现
/**
* 简单的 IoC 容器
* 管理 Bean 的创建和依赖注入
*/
class SimpleIocContainer {
private Map<String, Object> beans = new HashMap<>();
// 注册 Bean 到容器
public void register(String name, Object bean) {
beans.put(name, bean);
System.out.println("📦 注册 Bean: " + name);
}
// 从容器获取 Bean(硬编码方式)
public <T> T getBean(String name) {
if ("userService".equals(name)) {
Database database = (Database) beans.get("database");
return (T) new UserServiceWithIoc(database);
}
return (T) beans.get(name);
}
}
3.2 真实容器实现(使用反射)
/**
* 更真实的 IoC 容器
* 使用反射自动分析依赖关系
*/
class RealisticIocContainer {
private Map<String, Class<?>> typeRegistry = new HashMap<>();
private Map<String, Object> singletonCache = new HashMap<>();
public <T> T getBean(String name) {
Class<?> clazz = typeRegistry.get(name);
// 🔍 使用反射分析构造函数
Constructor<?> constructor = clazz.getConstructors()[0];
Class<?>[] paramTypes = constructor.getParameterTypes();
// 🔄 递归获取依赖 Bean
Object[] args = new Object[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++) {
String dependencyName = findBeanNameByType(paramTypes[i]);
args[i] = getBean(dependencyName); // 递归调用
}
// 🎯 使用反射创建 Bean
return (T) constructor.newInstance(args);
}
}
🎯 四、依赖注入的三种方式
依赖注入的方式是什么?
依赖注入的方式就是容器把依赖传递给对象的不同途径:
- 🏗️ 构造函数注入:通过构造函数参数传递依赖(推荐)
- ⚙️ Setter 注入:通过 setter 方法传递依赖
- 🔧 字段注入:直接注入到字段(不推荐)
为什么有不同的注入方式?
不同的注入方式适用于不同的场景:
- 🎯 强制依赖 → 构造函数注入(必须有才能创建对象)
- 🔄 可选依赖 → Setter 注入(可以后续设置)
- 🚫 避免使用 → 字段注入(破坏封装,难以测试)
4.1 构造函数注入(推荐)
@Component
public class UserService {
private final Database database; // final 确保不可变
// 推荐方式:通过构造函数注入
public UserService(Database database) {
this.database = database;
}
}
优点: 强制依赖、不可变、线程安全
4.2 Setter 注入
@Component
public class UserService {
private Database database;
@Autowired
public void setDatabase(Database database) {
this.database = database;
}
}
优点: 可选依赖、可重新配置
4.3 字段注入(不推荐)
@Component
public class UserService {
@Autowired
private Database database; // 直接注入字段
}
缺点: 难以测试、违反封装原则
🗂️ 五、IoC 配置的三种方式
IoC 配置是什么?
IoC 配置就是告诉容器如何管理 Bean 的说明书,包括:
- 📦 哪些类要成为 Bean(Bean 定义)
- 🔗 Bean 之间的依赖关系(谁需要谁)
- ⚙️ Bean 的创建方式和属性(如何创建、作用域等)
依赖注入是干什么的?
依赖注入就是容器自动把 Bean 需要的其他 Bean 传递给它:
- 🎯 解决依赖问题:A 需要 B,容器自动把 B 给 A
- 🔄 实现松耦合:A 不用自己创建 B,只需要接收 B
- 🧪 便于测试:可以轻松注入模拟对象进行测试
5.1 XML 配置(传统方式)
<!-- applicationContext.xml -->
<beans>
<bean id="database" class="com.example.Database"/>
<bean id="userService" class="com.example.UserServiceWithIoc">
<constructor-arg ref="database"/>
</bean>
</beans>
5.2 注解配置(现代主流)
@Component
public class Database { }
@Service
public class UserService {
@Autowired
public UserService(Database database) { }
}
常用注解:
@Component- 通用组件@Service- 业务服务层@Repository- 数据访问层@Controller- 控制器层@Autowired- 自动装配
5.3 Java 配置(Java Config)
@Configuration
public class AppConfig {
@Bean
public Database database() {
return new Database();
}
@Bean
public UserService userService(Database database) {
return new UserServiceWithIoc(database);
}
}
5.4 配置方式对比
| 特性 | XML 配置 | 注解配置 | Java 配置 |
|---|---|---|---|
| 学习难度 | 中等 | 简单 | 简单 |
| 配置位置 | 集中 | 分散 | 集中 |
| 类型安全 | ❌ | ✅ | ✅ |
| 编译检查 | ❌ | ✅ | ✅ |
| 适用场景 | 老项目维护 | 日常开发 | 复杂配置 |
🔄 六、Bean 的作用域和生命周期
6.1 Bean 作用域
Singleton(单例)- 默认
@Component
@Scope("singleton") // 可省略,默认就是单例
public class DatabaseService {
// 整个应用只有一个实例
}
Prototype(原型)
@Component
@Scope("prototype")
public class UserSession {
// 每次获取都创建新实例
}
6.2 Bean 生命周期
创建 Bean → 注入依赖 → 初始化 → 使用 → 销毁
↑ ↓
←——————— 容器全程管理 ————————————————→
🚀 七、真实 IoC 容器的核心技术
7.1 反射分析构造函数
// 真正的容器会用反射分析类的构造函数
Class<?> clazz = UserServiceWithIoc.class;
Constructor<?>[] constructors = clazz.getConstructors();
// 分析构造函数参数
Constructor<?> constructor = constructors[0];
Class<?>[] paramTypes = constructor.getParameterTypes();
// paramTypes[0] = Database.class
// 根据参数类型去容器中找对应的 Bean
Object[] args = new Object[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++) {
args[i] = getBean(paramTypes[i]); // 递归获取依赖 Bean
}
// 用反射创建 Bean
Object instance = constructor.newInstance(args);
7.2 注解驱动的依赖分析
// Spring 会扫描注解来识别 Bean
@Component // 告诉 Spring 这是一个 Bean
public class UserServiceWithIoc {
@Autowired // Spring 看到这个注解就知道要注入 Bean
public UserServiceWithIoc(Database database) {
this.database = database;
}
}
🚨 八、常见问题和解决方案
8.1 循环依赖问题
// 问题:A 依赖 B,B 依赖 A
@Component
public class ServiceA {
public ServiceA(ServiceB serviceB) { // 循环依赖!
this.serviceB = serviceB;
}
}
// 解决方案:使用 @Lazy 延迟加载
@Component
public class ServiceA {
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
}
8.2 多个实现的选择
// 接口
public interface PaymentService {
void pay(double amount);
}
// 多个实现
@Component("alipay")
public class AlipayService implements PaymentService { }
@Component("wechat")
public class WechatPayService implements PaymentService { }
// 使用时指定具体实现
@Component
public class OrderService {
public OrderService(@Qualifier("alipay") PaymentService paymentService) {
this.paymentService = paymentService;
}
}
📊 九、对比总结
| 特性 | 传统方式 | IoC 方式 |
|---|---|---|
| 耦合度 | 高(紧耦合) | 低(松耦合) |
| 可测试性 | 难以测试 | 易于测试 |
| 可维护性 | 难以维护 | 易于维护 |
| 灵活性 | 低 | 高 |
| 依赖管理 | 手动管理 | 容器管理 Bean |
🎓 十、核心理解
IoC 的本质
IoC 容器 = 智能的 Bean 工厂 + 自动依赖分析 + 反射创建对象
简化理解
- 把所有 Bean 放进一个 Map 中
- 需要的时候直接去 Map 取 Bean,而不用自己 new
- 容器负责分析 Bean 依赖关系并自动注入
实际应用
- Spring Framework 就是一个强大的 IoC 容器
- Spring Boot 进一步简化了 Bean 的使用
- 依赖注入 是现代 Java 开发的标准实践
💡 记住:Bean 就是被容器管理的对象,IoC 容器就是一个智能的 Bean 工厂!依赖注入就是通过构造函数参数传递对象,没有什么魔法!