IoC

49 阅读7分钟

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"?
  1. ☕ 咖啡豆的比喻:Java 的 Logo 是咖啡杯,Bean(豆子)是咖啡的原料
  2. 🧩 组件化思想:像豆子一样,是构成应用的基本"颗粒"
  3. 📦 可重用性:豆子可以种植、收获、重复使用
  4. 📜 历史来源:来自于 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 工厂 + 自动依赖分析 + 反射创建对象

简化理解

  1. 把所有 Bean 放进一个 Map 中
  2. 需要的时候直接去 Map 取 Bean,而不用自己 new
  3. 容器负责分析 Bean 依赖关系并自动注入

实际应用

  • Spring Framework 就是一个强大的 IoC 容器
  • Spring Boot 进一步简化了 Bean 的使用
  • 依赖注入 是现代 Java 开发的标准实践

💡 记住:Bean 就是被容器管理的对象,IoC 容器就是一个智能的 Bean 工厂!依赖注入就是通过构造函数参数传递对象,没有什么魔法!