IoC容器深度解析(一):从零理解IoC,手写100行代码实现简易容器!

系列文章第1篇 | 共3篇
难度:⭐⭐ | 适合人群:想深入理解Spring IoC的开发者


💥 开场:一次代码review的"灵魂拷问"

时间: 周三下午
地点: 会议室
事件: 代码评审

我: "这是我写的用户服务代码..."(信心满满)

public class UserService {
    
    private UserDao userDao = new UserDaoImpl();  // 直接new
    
    public User getUser(Long id) {
        return userDao.findById(id);
    }
}

架构师老李: "等等,为什么要在这里new一个UserDao?"

我: "不new怎么用?" 🤔

老李: "如果以后要换一个实现类呢?比如从MySQL换到MongoDB?"

我: "那就改代码呗..." 😅

老李: "一个类还好,如果100个类都这样new,要改100次?"

我: "这..." 😰(语塞)

老李: "这就是为什么需要IoC(控制反转)和DI(依赖注入)。你不应该自己new对象,应该让Spring容器帮你管理。"

我: "IoC?DI?听说过但不太懂..." 😓

老李: "回去好好研究一下Spring的IoC容器,下次评审我要看到改进。"


回到工位,我陷入了沉思...

我: "什么是IoC?为什么不让我new对象?Spring到底做了什么?" 🤔

经过一番研究后: "原来IoC这么重要!而且自己也能实现!" 💡


🤔 第一问:什么是IoC和DI?

Q1:先看一个生活中的例子

场景:你要喝咖啡

方式A:自己做(传统方式)

你需要:
1. 买咖啡豆
2. 买咖啡机
3. 买磨豆机
4. 买水
5. 学习如何制作
6. 自己做咖啡

累不累? 累死了!😫


方式B:去咖啡店(IoC方式)

你只需要:
1. 告诉店员:"我要一杯拿铁"
2. 等着接咖啡

爽不爽? 爽!😎


类比到代码:

现实世界代码世界
UserService
咖啡UserDao对象
自己做自己new
咖啡店Spring IoC容器
店员给你依赖注入

IoC的核心思想: "别自己做,我(Spring)给你准备好!"


Q2:什么是IoC(控制反转)?

官方定义:

Inversion of Control(控制反转)是一种设计原则,将对象的创建和管理权从程序代码转移到外部容器。

人话版本:

传统方式(你控制):

public class UserService {
    // 我自己创建依赖对象
    private UserDao userDao = new UserDaoImpl();
    
    // 我控制使用哪个实现类
}

IoC方式(容器控制):

public class UserService {
    // 我只声明需要什么
    private UserDao userDao;
    
    // 具体用哪个实现,由Spring容器决定
    // 容器会"注入"给我
}

控制反转了什么?

维度传统方式IoC方式
谁创建对象?你自己new容器创建
谁管理对象?你自己管理容器管理
谁决定依赖?代码中写死配置文件/注解
控制权在你手里反转给容器

总结: 控制权从"你"反转到"容器",这就是控制反转


Q3:什么是DI(依赖注入)?

官方定义:

Dependency Injection(依赖注入)是实现IoC的一种方式,通过外部注入依赖对象。

人话版本:

传统方式(主动获取):

public class UserService {
    private UserDao userDao;
    
    public UserService() {
        // 我主动去"拿"依赖
        this.userDao = new UserDaoImpl();
    }
}

DI方式(被动接收):

public class UserService {
    private UserDao userDao;
    
    // 构造器注入:容器"给"我依赖
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
}

依赖注入的三种方式:

方式1:构造器注入

@Service
public class UserService {
    
    private final UserDao userDao;
    
    @Autowired  // 容器通过构造器注入
    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }
}

方式2:Setter注入

@Service
public class UserService {
    
    private UserDao userDao;
    
    @Autowired  // 容器通过setter注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

方式3:字段注入

@Service
public class UserService {
    
    @Autowired  // 容器直接注入字段(反射)
    private UserDao userDao;
}

Q4:IoC和DI是什么关系?

简单理解:

IoC(控制反转)
    ↓
  是一种思想/原则
    ↓
DI(依赖注入)
    ↓
  是实现IoC的一种方式

类比:

  • IoC = "我要喝咖啡"(目标)
  • DI = "店员给我咖啡"(实现方式)

记忆口诀:

IoC是思想,DI是手段;
控制要反转,依赖靠注入。


🎨 第二问:为什么需要IoC?

痛点1:代码耦合度高

没有IoC的代码:

public class UserService {
    private UserDao userDao = new UserDaoMySQLImpl();  // 写死了
    
    public User getUser(Long id) {
        return userDao.findById(id);
    }
}

问题:

  • ❌ 如果要换成MongoDB实现,必须改代码
  • ❌ 如果要换成Redis实现,又要改代码
  • ❌ 100个地方用了,要改100次

有IoC的代码:

@Service
public class UserService {
    
    @Autowired
    private UserDao userDao;  // 不指定具体实现
    
    public User getUser(Long id) {
        return userDao.findById(id);
    }
}

// 配置文件或@Configuration中决定用哪个实现
@Bean
public UserDao userDao() {
    return new UserDaoMySQLImpl();  // 只需改这一处
    // return new UserDaoMongoDBImpl();  // 换实现只改这里
}

优势: ✅ 解耦!改实现只需改配置,不改业务代码!


痛点2:对象创建复杂

没有IoC:

public class OrderService {
    
    public Order createOrder() {
        // 创建一个Order需要很多依赖
        UserDao userDao = new UserDaoImpl();
        ProductDao productDao = new ProductDaoImpl();
        InventoryService inventoryService = new InventoryService(productDao);
        PaymentService paymentService = new PaymentService();
        NotificationService notificationService = new NotificationService();
        
        // 天啊,这才能开始创建Order...
        Order order = new Order();
        // ...
        return order;
    }
}

问题:

  • ❌ 创建一个对象要new一堆依赖
  • ❌ 依赖关系复杂,容易遗漏
  • ❌ 代码冗长,难以维护

有IoC:

@Service
public class OrderService {
    
    @Autowired private UserDao userDao;
    @Autowired private ProductDao productDao;
    @Autowired private InventoryService inventoryService;
    @Autowired private PaymentService paymentService;
    @Autowired private NotificationService notificationService;
    
    public Order createOrder() {
        // 所有依赖都准备好了,直接用!
        Order order = new Order();
        // ...
        return order;
    }
}

优势: ✅ 简洁!容器帮你管理所有依赖!


痛点3:单例难以控制

没有IoC:

// 手写单例
public class ConfigService {
    private static ConfigService instance;
    
    private ConfigService() {}
    
    public static ConfigService getInstance() {
        if (instance == null) {
            synchronized (ConfigService.class) {
                if (instance == null) {
                    instance = new ConfigService();
                }
            }
        }
        return instance;
    }
}

问题:

  • ❌ 单例模式代码繁琐
  • ❌ 多线程安全要自己处理
  • ❌ 每个单例类都要写一遍

有IoC:

@Service
@Scope("singleton")  // 默认就是单例
public class ConfigService {
    // 啥都不用写,Spring自动管理单例
}

优势: ✅ 自动单例!不用写繁琐代码!


💻 第三问:手写100行代码实现简易IoC容器

目标

实现一个最简单的IoC容器,支持:

  1. ✅ Bean的注册
  2. ✅ Bean的创建
  3. ✅ 依赖注入(构造器注入)
  4. ✅ 单例管理

实现步骤

步骤1:定义Bean定义类

package com.example.myioc;

/**
 * Bean定义类
 * 存储Bean的元数据信息
 */
public class BeanDefinition {
    
    private String beanName;        // Bean名称
    private Class<?> beanClass;     // Bean类型
    private boolean singleton;      // 是否单例
    
    public BeanDefinition(String beanName, Class<?> beanClass) {
        this.beanName = beanName;
        this.beanClass = beanClass;
        this.singleton = true;  // 默认单例
    }
    
    // Getter和Setter
    public String getBeanName() {
        return beanName;
    }
    
    public Class<?> getBeanClass() {
        return beanClass;
    }
    
    public boolean isSingleton() {
        return singleton;
    }
    
    public void setSingleton(boolean singleton) {
        this.singleton = singleton;
    }
}

步骤2:实现简易IoC容器

package com.example.myioc;

import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 简易IoC容器
 * 核心功能:Bean注册、创建、依赖注入
 */
public class SimpleIoCContainer {
    
    // 存储Bean定义(配方)
    private Map<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();
    
    // 存储单例Bean实例(成品)
    private Map<String, Object> singletonBeans = new ConcurrentHashMap<>();
    
    /**
     * 注册Bean定义
     */
    public void registerBeanDefinition(String beanName, Class<?> beanClass) {
        BeanDefinition definition = new BeanDefinition(beanName, beanClass);
        beanDefinitions.put(beanName, definition);
        System.out.println("注册Bean: " + beanName + " -> " + beanClass.getSimpleName());
    }
    
    /**
     * 获取Bean实例
     */
    public Object getBean(String beanName) {
        // 1. 检查是否已注册
        if (!beanDefinitions.containsKey(beanName)) {
            throw new RuntimeException("Bean不存在: " + beanName);
        }
        
        BeanDefinition definition = beanDefinitions.get(beanName);
        
        // 2. 如果是单例,先从缓存中获取
        if (definition.isSingleton() && singletonBeans.containsKey(beanName)) {
            System.out.println("从缓存获取单例Bean: " + beanName);
            return singletonBeans.get(beanName);
        }
        
        // 3. 创建Bean实例
        Object bean = createBean(definition);
        
        // 4. 如果是单例,放入缓存
        if (definition.isSingleton()) {
            singletonBeans.put(beanName, bean);
            System.out.println("单例Bean已缓存: " + beanName);
        }
        
        return bean;
    }
    
    /**
     * 创建Bean实例(核心方法)
     */
    private Object createBean(BeanDefinition definition) {
        Class<?> beanClass = definition.getBeanClass();
        
        try {
            // 1. 获取所有构造器
            Constructor<?>[] constructors = beanClass.getConstructors();
            
            if (constructors.length == 0) {
                // 没有公共构造器,使用默认构造器
                return beanClass.getDeclaredConstructor().newInstance();
            }
            
            // 2. 使用第一个构造器(简化处理)
            Constructor<?> constructor = constructors[0];
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            
            if (parameterTypes.length == 0) {
                // 无参构造器
                System.out.println("使用无参构造器创建: " + beanClass.getSimpleName());
                return constructor.newInstance();
            }
            
            // 3. 有参构造器,需要注入依赖(依赖注入的核心!)
            System.out.println("使用有参构造器创建: " + beanClass.getSimpleName());
            Object[] args = new Object[parameterTypes.length];
            
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> paramType = parameterTypes[i];
                // 根据类型查找对应的Bean
                String dependencyBeanName = findBeanNameByType(paramType);
                if (dependencyBeanName != null) {
                    // 递归获取依赖的Bean(依赖注入)
                    args[i] = getBean(dependencyBeanName);
                    System.out.println("  注入依赖: " + paramType.getSimpleName());
                } else {
                    throw new RuntimeException("找不到类型为 " + paramType + " 的Bean");
                }
            }
            
            return constructor.newInstance(args);
            
        } catch (Exception e) {
            throw new RuntimeException("创建Bean失败: " + beanClass, e);
        }
    }
    
    /**
     * 根据类型查找Bean名称
     */
    private String findBeanNameByType(Class<?> type) {
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitions.entrySet()) {
            Class<?> beanClass = entry.getValue().getBeanClass();
            // 检查是否是该类型或其子类
            if (type.isAssignableFrom(beanClass)) {
                return entry.getKey();
            }
        }
        return null;
    }
    
    /**
     * 获取所有Bean名称
     */
    public void printAllBeans() {
        System.out.println("\n===== 容器中的所有Bean =====");
        beanDefinitions.keySet().forEach(name -> 
            System.out.println("- " + name)
        );
        System.out.println("============================\n");
    }
}

步骤3:创建测试类

UserDao接口和实现:

package com.example.myioc.demo;

// DAO接口
public interface UserDao {
    String findUserById(Long id);
}

// DAO实现
public class UserDaoImpl implements UserDao {
    
    public UserDaoImpl() {
        System.out.println("UserDaoImpl 被创建");
    }
    
    @Override
    public String findUserById(Long id) {
        return "User-" + id;
    }
}

UserService:

package com.example.myioc.demo;

public class UserService {
    
    private final UserDao userDao;
    
    // 构造器注入
    public UserService(UserDao userDao) {
        System.out.println("UserService 被创建,依赖已注入");
        this.userDao = userDao;
    }
    
    public String getUser(Long id) {
        return userDao.findUserById(id);
    }
}

OrderService(依赖UserService):

package com.example.myioc.demo;

public class OrderService {
    
    private final UserService userService;
    
    // 构造器注入
    public OrderService(UserService userService) {
        System.out.println("OrderService 被创建,依赖已注入");
        this.userService = userService;
    }
    
    public String createOrder(Long userId) {
        String user = userService.getUser(userId);
        return "Order created for " + user;
    }
}

步骤4:测试我们的IoC容器

package com.example.myioc;

import com.example.myioc.demo.*;

public class IoCTest {
    
    public static void main(String[] args) {
        System.out.println("========== 开始测试简易IoC容器 ==========\n");
        
        // 1. 创建IoC容器
        SimpleIoCContainer container = new SimpleIoCContainer();
        
        // 2. 注册Bean定义
        System.out.println("--- 步骤1:注册Bean ---");
        container.registerBeanDefinition("userDao", UserDaoImpl.class);
        container.registerBeanDefinition("userService", UserService.class);
        container.registerBeanDefinition("orderService", OrderService.class);
        
        // 3. 查看所有Bean
        container.printAllBeans();
        
        // 4. 获取Bean(测试依赖注入)
        System.out.println("--- 步骤2:获取OrderService ---");
        OrderService orderService = (OrderService) container.getBean("orderService");
        
        // 5. 使用Bean
        System.out.println("\n--- 步骤3:使用Bean ---");
        String result = orderService.createOrder(123L);
        System.out.println("结果: " + result);
        
        // 6. 再次获取(测试单例)
        System.out.println("\n--- 步骤4:再次获取OrderService(测试单例) ---");
        OrderService orderService2 = (OrderService) container.getBean("orderService");
        System.out.println("是否是同一个实例: " + (orderService == orderService2));
        
        System.out.println("\n========== 测试完成 ==========");
    }
}

运行结果

========== 开始测试简易IoC容器 ==========

--- 步骤1:注册Bean ---
注册Bean: userDao -> UserDaoImpl
注册Bean: userService -> UserService
注册Bean: orderService -> OrderService

===== 容器中的所有Bean =====
- userDao
- userService
- orderService
============================

--- 步骤2:获取OrderService ---
使用有参构造器创建: OrderService
使用有参构造器创建: UserService
  注入依赖: UserDao
使用无参构造器创建: UserDaoImpl
UserDaoImpl 被创建
单例Bean已缓存: userDao
  注入依赖: UserService
UserService 被创建,依赖已注入
单例Bean已缓存: userService
  注入依赖: UserService
OrderService 被创建,依赖已注入
单例Bean已缓存: orderService

--- 步骤3:使用Bean ---
结果: Order created for User-123

--- 步骤4:再次获取OrderService(测试单例) ---
从缓存获取单例Bean: orderService
是否是同一个实例: true

========== 测试完成 ==========

代码解析

关键点1:Bean定义的存储

// 存储Bean的"配方"
private Map<String, BeanDefinition> beanDefinitions = new ConcurrentHashMap<>();

关键点2:单例缓存

// 存储单例Bean实例的"仓库"
private Map<String, Object> singletonBeans = new ConcurrentHashMap<>();

关键点3:依赖注入的实现

// 创建Bean时,递归获取依赖
for (int i = 0; i < parameterTypes.length; i++) {
    String dependencyBeanName = findBeanNameByType(parameterTypes[i]);
    args[i] = getBean(dependencyBeanName);  // 递归!
}

流程图:

用户调用 getBean("orderService")
    ↓
检查单例缓存(没有)
    ↓
创建 OrderService
    ↓
发现需要 UserService 依赖
    ↓
递归调用 getBean("userService")
    ↓
创建 UserService
    ↓
发现需要 UserDao 依赖
    ↓
递归调用 getBean("userDao")
    ↓
创建 UserDaoImpl(无依赖)
    ↓
返回 UserDaoImpl,缓存
    ↓
返回 UserService,缓存
    ↓
返回 OrderService,缓存

我们实现了什么?

IoC(控制反转)

  • Bean的创建权交给了容器
  • 不再是用户自己new

DI(依赖注入)

  • 通过构造器自动注入依赖
  • 递归解决依赖链

单例管理

  • 自动缓存单例Bean
  • 第二次获取直接从缓存返回

只用了100行代码!


🔍 第四问:Spring的BeanFactory是什么?

从我们的简易版到Spring

我们的容器:

SimpleIoCContainer container = new SimpleIoCContainer();
container.registerBeanDefinition("userDao", UserDaoImpl.class);
Object bean = container.getBean("userDao");

Spring的容器:

BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
// 或
BeanFactory factory = new DefaultListableBeanFactory();
Object bean = factory.getBean("userDao");

发现: 接口名都叫BeanFactory!思路是一样的!


BeanFactory接口

位置: org.springframework.beans.factory.BeanFactory

核心方法:

public interface BeanFactory {
    
    /**
     * 根据名称获取Bean
     */
    Object getBean(String name) throws BeansException;
    
    /**
     * 根据名称和类型获取Bean
     */
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    
    /**
     * 根据类型获取Bean
     */
    <T> T getBean(Class<T> requiredType) throws BeansException;
    
    /**
     * 检查是否包含指定名称的Bean
     */
    boolean containsBean(String name);
    
    /**
     * 检查指定Bean是否单例
     */
    boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
    
    /**
     * 检查指定Bean是否原型
     */
    boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
    
    /**
     * 获取Bean的类型
     */
    Class<?> getType(String name) throws NoSuchBeanDefinitionException;
}

看到了吗? 和我们的简易版接口类似!


BeanFactory的核心实现类

1. DefaultListableBeanFactory

最常用的实现类,几乎所有功能都有:

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

// 注册Bean
BeanDefinition definition = new RootBeanDefinition(UserService.class);
factory.registerBeanDefinition("userService", definition);

// 获取Bean
UserService service = factory.getBean("userService", UserService.class);

2. XmlBeanFactory(已废弃)

通过XML配置文件创建:

// beans.xml
<beans>
    <bean id="userDao" class="com.example.UserDaoImpl"/>
    <bean id="userService" class="com.example.UserService">
        <constructor-arg ref="userDao"/>
    </bean>
</beans>

// Java代码
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
UserService service = (UserService) factory.getBean("userService");

注意: Spring 3.1后已废弃,推荐用ApplicationContext


手写示例:使用BeanFactory

package com.example.spring;

import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;

public class BeanFactoryDemo {
    
    public static void main(String[] args) {
        System.out.println("===== BeanFactory演示 =====\n");
        
        // 1. 创建BeanFactory
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
        
        // 2. 注册Bean定义
        System.out.println("--- 注册Bean定义 ---");
        factory.registerBeanDefinition("userDao", 
            new RootBeanDefinition(UserDaoImpl.class));
        factory.registerBeanDefinition("userService", 
            new RootBeanDefinition(UserService.class));
        
        // 3. 获取Bean
        System.out.println("\n--- 第一次获取Bean ---");
        UserService service1 = factory.getBean("userService", UserService.class);
        service1.getUser(1L);
        
        // 4. 再次获取(测试单例)
        System.out.println("\n--- 第二次获取Bean ---");
        UserService service2 = factory.getBean("userService", UserService.class);
        System.out.println("是否同一实例: " + (service1 == service2));
        
        // 5. 检查Bean信息
        System.out.println("\n--- Bean信息 ---");
        System.out.println("包含userService: " + factory.containsBean("userService"));
        System.out.println("是否单例: " + factory.isSingleton("userService"));
        System.out.println("Bean类型: " + factory.getType("userService"));
        
        System.out.println("\n===== 演示完成 =====");
    }
}

输出:

===== BeanFactory演示 =====

--- 注册Bean定义 ---

--- 第一次获取Bean ---
UserDaoImpl 被创建
UserService 被创建,依赖已注入
User-1

--- 第二次获取Bean ---
是否同一实例: true

--- Bean信息 ---
包含userService: true
是否单例: true
Bean类型: class com.example.UserService

===== 演示完成 =====

BeanFactory的特点

特点说明
懒加载调用getBean()时才创建Bean
轻量级功能基础,占用内存少
手动配置需要手动注册Bean定义
适用场景资源受限环境、嵌入式系统

🎯 第五问:BeanFactory vs 我们的简易版

功能对比

功能我们的简易版Spring BeanFactory
Bean注册✅ 支持✅ 支持
Bean创建✅ 支持✅ 支持
依赖注入✅ 构造器注入✅ 构造器+Setter+字段
单例管理✅ 支持✅ 支持
原型模式❌ 不支持✅ 支持
生命周期回调❌ 不支持✅ 支持
循环依赖❌ 不支持✅ 支持(三级缓存)
AOP集成❌ 不支持✅ 支持
事件机制❌ 不支持❌ 不支持(需ApplicationContext)

设计对比

我们的设计:

SimpleIoCContainer
├── beanDefinitions (Bean定义)
├── singletonBeans (单例缓存)
├── registerBeanDefinition() (注册)
├── getBean() (获取)
└── createBean() (创建)

Spring的设计:

BeanFactory (接口)
├── HierarchicalBeanFactory (层次化)
├── ListableBeanFactory (可列举)
└── DefaultListableBeanFactory (默认实现)
    ├── beanDefinitionMap (Bean定义)
    ├── singletonObjects (一级缓存)
    ├── earlySingletonObjects (二级缓存)
    ├── singletonFactories (三级缓存)
    └── 各种BeanPostProcessor

差距: Spring的设计更复杂,但也更强大!


💡 知识点总结

本篇你学到了什么?

IoC和DI的概念

  • IoC = 控制反转(思想)
  • DI = 依赖注入(实现方式)
  • 控制权从代码转移到容器

为什么需要IoC?

  • 解耦:降低代码耦合度
  • 简化:容器管理对象创建
  • 单例:自动管理单例模式

手写简易IoC容器

  • 100行代码实现核心功能
  • 理解了Bean注册、创建、注入流程
  • 实现了单例缓存机制

Spring的BeanFactory

  • 最基础的IoC容器接口
  • DefaultListableBeanFactory是核心实现
  • 懒加载、轻量级特点

简易版vs Spring

  • 思路相同,实现不同
  • Spring功能更强大
  • 理解原理更重要

🤔 思考题

问题1: 我们的简易IoC容器只支持构造器注入,如何改造支持Setter注入?

问题2: 如果两个Bean互相依赖(循环依赖),我们的容器会怎样?Spring是如何解决的?

问题3: BeanFactory和ApplicationContext有什么区别?为什么日常开发中都用ApplicationContext?

提示: 第二篇会详细解答!


📢 下期预告

《IoC容器深度解析(二):ApplicationContext深入剖析,7大区别你知道几个?》

下一篇我们将:

  • 深入ApplicationContext接口
  • 对比BeanFactory和ApplicationContext的7大区别
  • 分析ApplicationContext的启动流程
  • 实战:如何选择合适的容器
  • 源码解析:ApplicationContext是如何扩展BeanFactory的

让你彻底搞懂Spring的两大核心容器! 🚀


💬 互动时间

你在实际开发中遇到过IoC相关的问题吗?
对我们手写的简易IoC容器有什么改进建议?
还有哪些关于IoC的疑问?

欢迎评论区讨论!💭


觉得有帮助?三连支持: 👍 点赞 | ⭐ 收藏 | 🔄 转发

看完这篇,IoC不再神秘!


下一篇见! 👋