【开源项目】手写迷你版 Spring IoC 容器,帮你彻底理解 Spring 核心原理!
【开源项目】手写迷你版 Spring IoC 容器,帮你彻底理解 Spring 核心原理!🚀
📖 前言
你是否曾经好奇:
- Spring 的 IoC 容器到底是怎么工作的?
- @Autowired 是如何实现自动注入的?
- Bean 的生命周期管理有什么奥秘?
阅读源码太复杂?理论讲解太抽象?
别担心!我为你准备了一个超轻量级的 Spring 实现 —— Mini-Spring,用最简洁的代码(仅几百行),带你从零理解 Spring 的核心机制!
🎯 项目介绍
Mini-Spring 是一个简化版的 Spring IoC 容器实现,专注于帮助开发者理解 Spring 最核心的两个概念:
- IoC(控制反转) - 容器管理对象的生命周期
- DI(依赖注入) - 自动装配 Bean 之间的依赖关系
✨ 核心特性
✅ 自动扫描 - 基于 @Component 注解自动发现并注册组件
✅ 依赖注入 - 支持 @Autowired 字段级别的自动注入
✅ 生命周期管理 - 单例/原型模式,通过 @Scope 灵活配置
✅ 零依赖 - 纯 Java 实现,无需任何第三方库
✅ 代码精简 - 核心代码不到 500 行,通俗易懂
💡 为什么需要这个项目?
痛点分析
| 学习方式 | 问题 |
|---|---|
| 直接读 Spring 源码 | 代码量巨大,设计复杂,新手难以入门 |
| 看理论文章 | 抽象难懂,缺乏实践验证 |
| 使用 Spring Boot | 开箱即用,但不知道底层原理 |
Mini-Spring 的优势
🎓 学习曲线平缓 - 从注解定义到容器实现,每一步都清晰可见
🔍 代码透明 - 没有复杂的抽象层,直接看到核心逻辑
⚡ 快速上手 - 5 分钟运行示例,立即看到效果
🛠️ 可扩展 - 基于此项目可以继续添加 AOP、事务等功能
🚀 快速开始
1️⃣ 克隆项目
git clone https://gitee.com/jiucenglou/mini-spring.git
cd mini-spring
2️⃣ 编译运行
mvn clean compile
java -cp target/classes com.minispring.test.MiniSpringTest
3️⃣ 查看输出
========================================
Mini-Spring 框架演示
========================================
【步骤 1】启动 IoC 容器,扫描包: com.minispring.example
✓ Bean 创建成功: userService (UserService)
→ 注入依赖: userService = userService
✓ Bean 创建成功: orderService (OrderService)
→ 注入依赖: orderService = orderService
✓ Bean 创建成功: userController (UserController)
【步骤 2】容器中所有 Bean:
- userController
- orderService
- userService
【步骤 3】从容器中获取 Bean 并调用方法
✓ UserController 获取成功
=== 用户信息 ===
用户信息:张三,年龄 25
=== 订单信息 ===
订单信息:订单号 12345,金额 99.99
=== 业务流程 ===
创建用户: 李四
创建订单: iPhone 15
业务操作完成!
【步骤 4】验证单例模式
controller1 == controller2: true
✓ 单例模式验证通过:两次获取的是同一个实例
🔥 核心实现解析
架构概览
┌─────────────────────────────────────────┐
│ ApplicationContext │
│ (IoC 容器核心) │
├─────────────────────────────────────────┤
│ │
│ 1. ClassPathScanner 扫描包 │
│ ↓ │
│ 2. 注册 BeanDefinition │
│ ↓ │
│ 3. 反射实例化 Bean │
│ ↓ │
│ 4. @Autowired 依赖注入 │
│ ↓ │
│ 5. 缓存单例 Bean │
│ │
└─────────────────────────────────────────┘
关键代码揭秘
📌 1. 自动扫描与注册
public ApplicationContext(String basePackage) {
// 1. 扫描包,找到所有 @Component 类
List<Class<?>> classes = scanner.scan(basePackage);
// 2. 注册 Bean 定义
for (Class<?> clazz : classes) {
registerBeanDefinition(clazz);
}
// 3. 实例化所有单例 Bean
instantiateAllSingletons();
}
工作流程:
- 使用
ClassPathScanner扫描指定包路径 - 查找所有带有
@Component注解的类 - 为每个类创建
BeanDefinition对象 - 根据
@Scope注解设置作用域(默认单例)
📌 2. 依赖注入核心逻辑
private void injectDependencies(Object instance) {
Field[] fields = instance.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Autowired.class)) {
// 打破私有字段访问限制
field.setAccessible(true);
// 根据类型获取 Bean
Class<?> fieldType = field.getType();
String beanName = getBeanName(fieldType);
Object dependency = getBean(beanName);
// 注入依赖
field.set(instance, dependency);
}
}
}
关键点:
setAccessible(true)- 突破 private 限制,允许访问私有字段field.set(instance, dependency)- 使用反射进行字段赋值- 递归注入 - 自动处理依赖链,先创建被依赖的 Bean
📌 3. 单例模式实现
// 单例缓存
private Map<String, Object> singletonBeans = new HashMap<>();
private Object createBean(BeanDefinition beanDefinition) {
String beanName = beanDefinition.getBeanName();
// 已存在则直接返回
if (singletonBeans.containsKey(beanName)) {
return singletonBeans.get(beanName);
}
// 创建新实例
Class<?> clazz = beanDefinition.getBeanClass();
Object instance = clazz.getDeclaredConstructor().newInstance();
// 依赖注入
injectDependencies(instance);
// 缓存到单例池
singletonBeans.put(beanName, instance);
return instance;
}
设计思路:
- 首次创建时实例化并缓存
- 后续请求直接从缓存返回
- 保证整个容器中只有一个实例
📚 使用示例
定义服务层
@Component
public class UserService {
public String getUserInfo() {
return "用户信息:张三,年龄 25";
}
public void createUser(String name) {
System.out.println("创建用户: " + name);
}
}
@Component
public class OrderService {
public String getOrderInfo() {
return "订单信息:订单号 12345,金额 99.99";
}
public void createOrder(String productName) {
System.out.println("创建订单: " + productName);
}
}
控制器注入服务
@Component
public class UserController {
@Autowired
private UserService userService;
@Autowired
private OrderService orderService;
public void showUserInfo() {
System.out.println("\n=== 用户信息 ===");
System.out.println(userService.getUserInfo());
}
public void showOrderInfo() {
System.out.println("\n=== 订单信息 ===");
System.out.println(orderService.getOrderInfo());
}
public void businessOperation() {
System.out.println("\n=== 业务流程 ===");
userService.createUser("李四");
orderService.createOrder("iPhone 15");
System.out.println("业务操作完成!");
}
}
启动容器
public class MiniSpringTest {
public static void main(String[] args) {
// 一行代码启动 IoC 容器
ApplicationContext context = new ApplicationContext("com.example");
// 获取 Bean,直接使用
UserController controller = context.getBean(UserController.class);
// 调用业务方法
controller.showUserInfo();
controller.showOrderInfo();
controller.businessOperation();
}
}
对比传统方式:
// ❌ 传统方式 - 手动管理依赖
UserService userService = new UserService();
OrderService orderService = new OrderService();
UserController controller = new UserController(userService, orderService);
// ✅ Mini-Spring - 容器自动管理
ApplicationContext context = new ApplicationContext("com.example");
UserController controller = context.getBean(UserController.class);
优势:
- 无需手动
new对象 - 依赖关系自动维护
- 便于单元测试和替换实现
🎓 学习路径
Level 1: 理解基础(30 分钟)
- ✅ 运行测试程序,观察输出
- ✅ 理解 @Component、@Autowired、@Scope 的作用
- ✅ 了解 IoC 和 DI 的概念
关键问题:
- 什么是控制反转?(把对象的创建权交给容器)
- 什么是依赖注入?(容器自动填充依赖的对象)
- 为什么要用单例?(节省内存,共享状态)
Level 2: 阅读源码(2 小时)
建议顺序:
-
注解定义(
annotation包)@Component- 标记组件@Autowired- 自动注入@Scope- 作用域配置
-
BeanDefinition - Bean 的元数据
- 存储 Bean 名称、类型、作用域
- 记录实例化和初始化状态
-
ClassPathScanner - 如何扫描类
- 读取 classpath 下的 .class 文件
- 解析字节码判断是否有注解
-
ApplicationContext - 容器核心逻辑(重点!)
- 构造函数:扫描 → 注册 → 实例化
createBean():反射创建对象injectDependencies():依赖注入实现getBean():获取 Bean 的逻辑
Level 3: 动手实验(1 天)
- 添加新的 Service 类,体验自动扫描
- 创建更复杂的依赖关系树(A→B→C)
- 测试
@Scope("prototype")的行为差异 - 故意制造循环依赖(A→B→A),观察问题
- 修改代码,尝试不同的注入场景
实验建议:
// 尝试原型模式
@Component
@Scope("prototype")
public class PrototypeService {
// 每次获取都是新实例
}
// 测试
PrototypeService s1 = context.getBean(PrototypeService.class);
PrototypeService s2 = context.getBean(PrototypeService.class);
System.out.println(s1 == s2); // false
Level 4: 扩展功能(挑战)
- 💪 构造器注入 - 支持通过构造函数传递依赖
- 💪 @PostConstruct - Bean 初始化后的回调
- 💪 @PreDestroy - Bean 销毁前的清理
- 💪 循环依赖检测 - 检测并报告循环依赖
- 💪 @Qualifier - 解决同类型多个 Bean 的歧义
- 💪 简单 AOP - 使用动态代理实现切面
🌟 项目亮点
1. 代码极简
核心文件只有 7 个:
src/main/java/com/minispring/
├── annotation/
│ ├── Component.java # 组件注解
│ ├── Autowired.java # 自动注入注解
│ └── Scope.java # 作用域注解
├── beans/
│ └── BeanDefinition.java # Bean 定义
├── context/
│ ├── ApplicationContext.java # 容器核心 (~230 行)
│ └── ClassPathScanner.java # 扫描器 (~80 行)
└── example/ # 示例代码
├── controller/
└── service/
总代码量:不到 500 行!
2. 注释详细
每个关键步骤都有中文注释,例如依赖注入的实现:
// 【第1步】打破权限限制
// 因为字段通常是 private 的,外部无法直接访问
// setAccessible(true) 告诉 JVM:"我有特权访问这个私有字段"
field.setAccessible(true);
// 【第2步】获取字段的类型
// 例如:private UserService userService; → fieldType = UserService.class
Class<?> fieldType = field.getType();
// 【第3步】根据类型生成 Bean 名称
// 规则:类名首字母小写
// 例如:UserService.class → "userService"
String fieldBeanName = getBeanName(fieldType);
// 【第4步】从容器中获取依赖的 Bean 实例
// 这可能会递归触发依赖 Bean 的创建和注入
Object dependency = getBean(fieldBeanName);
// 【第5步】通过反射将依赖注入到字段中
try {
// 核心操作:相当于执行 instance.field = dependency
// 但因为是反射,所以写成 field.set(instance, dependency)
field.set(instance, dependency);
System.out.println(" → 注入依赖: " + field.getName() +
" = " + fieldBeanName);
} catch (IllegalAccessException e) {
throw new RuntimeException("注入依赖失败: " + field.getName(), e);
}
3. 配套文档齐全
- 📘 README.md - 完整的使用说明和功能介绍
- 📗 ARCHITECTURE.md - 架构设计与工作流程详解
- 📙 QUICKSTART.md - 5 分钟快速入门指南
- 📕 本文档 - CSDN 推广文章
4. 真实可运行
不是伪代码,是完整可运行的项目:
- Maven 构建配置
- 完整的示例代码
- 测试用例验证
- 清晰的输出日志
⚠️ 注意事项
这是一个教学用途的简化实现,与真实 Spring 框架的差距:
| 功能 | Mini-Spring | Spring Framework |
|---|---|---|
| 依赖注入方式 | 仅字段注入 | 字段/构造器/setter 三种 |
| 作用域 | 单例/原型 2 种 | singleton/prototype/request/session 等 7 种 |
| AOP | ❌ | ✅ 完整的面向切面编程 |
| 事务管理 | ❌ | ✅ @Transactional |
| 循环依赖处理 | ❌ 会报错 | ✅ 三级缓存解决 |
| 条件装配 | ❌ | ✅ @Conditional |
| Bean 后置处理器 | ❌ | ✅ BeanPostProcessor |
| 事件机制 | ❌ | ✅ ApplicationEvent |
| 国际化 | ❌ | ✅ MessageSource |
| 资源加载 | ❌ | ✅ ResourceLoader |
但这正是优势所在 - 去掉所有复杂性,只保留最核心的 IoC/DI 机制,让你专注理解本质!
学习建议:
- 先用 Mini-Spring 理解核心概念
- 再阅读 Spring 源码,会发现很多熟悉的内容
- 最后理解 Spring 的高级特性和设计模式
🔗 项目地址
📦 gitee: “https://gitee.com/jiucenglou/mini-spring.git”
欢迎:
- ⭐ Star 支持 - 让更多人看到这个项目
- 🐛 提交 Issue - 发现问题或提出建议
- 🔧 Pull Request - 贡献代码或改进文档
- 📢 分享给更多学习者 - 帮助他人理解 Spring
💬 常见问题
Q1: 学完这个能看懂 Spring 源码吗?
A: 能理解 60%-70% 的核心逻辑。Spring 的真实实现更复杂,包括:
- 三级缓存解决循环依赖
- BeanPostProcessor 扩展点
- 各种 Aware 接口
- FactoryBean 特殊处理
但核心思想是一样的:扫描 → 注册 → 实例化 → 注入。
Q2: 为什么不实现构造器注入?
A: 为了保持代码简洁。构造器注入需要:
- 解析构造函数参数
- 处理参数类型匹配
- 解决参数依赖顺序
这些会增加代码复杂度,偏离"极简"的目标。你可以作为扩展练习自己实现!
Q3: 如何处理循环依赖?
A: Mini-Spring 目前不支持循环依赖(A→B→A)。如果尝试会抛出异常。
Spring 使用三级缓存解决:
- singletonObjects - 完全初始化的单例
- earlySingletonObjects - 早期引用(未完全初始化)
- singletonFactories - 工厂对象
这也是一个很好的扩展练习!
🎉 总结
Mini-Spring 用最简单的方式实现了 Spring 的核心功能:
🎯 自动扫描 - 发现并注册组件
🎯 依赖注入 - 自动装配 Bean 依赖
🎯 生命周期管理 - 单例/原型模式
核心价值:
- 📖 降低学习门槛 - 从几千行源码降到几百行
- 🔍 透明化实现 - 没有黑盒,每行代码都可追踪
- ⚡ 快速验证 - 5 分钟运行,立即看到效果
- 🛠️ 可扩展性强 - 在此基础上继续探索
无论你是:
- 📖 初学者 - 想理解 Spring 原理
- 👨🏫 讲师 - 需要教学示例
- 🔧 面试官 - 考察候选人对 IoC 的理解
- 💼 求职者 - 准备 Spring 相关面试
这个项目都能帮到你!
动手跑一遍代码,胜过读十篇理论文章! 🚀
相关文章
🙏 致谢
感谢 Spring 团队的优秀设计,让这个项目成为可能。
感谢所有为开源社区贡献的开发者们。
觉得有用?欢迎点赞、收藏、转发!有任何问题,评论区见!👇
让我们一起揭开 Spring 的神秘面纱! ✨
作者: [绝知此事]
许可证: MIT License
最后更新: 2026年5月9日
如果你觉得这个项目对你有帮助,请给个 Star ⭐ 支持一下!
更多推荐




所有评论(0)