手写Spring框架:IOC容器和DI注解的完整实现
·
🌱 手写Spring框架:IOC容器和DI注解的完整实现
一个从零开始实现的轻量级Spring框架,聚焦Spring IOC/DI最核心的设计与落地
📋 项目概述
本项目手写了一个精简版的 Spring 容器,覆盖以下能力:
- 🔧 IoC(控制反转):基于注解和 XML 配置自动创建、管理 Bean 的生命周期
- 💉 DI(依赖注入):支持多策略的依赖装配(@Autowired/@Qualifier/@Primary/@Resource/@Value)
- 📦 Bean 定义管理:通过
BeanDefine建立接口、实现类、别名之间的映射 - ⚙️ XML/注解双配置:同一容器兼容
<bean>标签手动注册与注解扫描
适合用于:
- 理解 Spring 核心机制:容器刷新流程、注解驱动的装配策略
- 拆解 IoC/DI 的关键步骤:包扫描 → Bean 注册 → 属性注入 → 生命周期回调
- 构建可拓展的轻量级容器,为后续功能(AOP、事务等)打基础
📂 目录结构与职责
MySpringAnno/
├── src/
│ ├── applicationContext.xml # 容器配置:扫描范围与 XML Bean 声明
│ └── org/sradon/
│ ├── mySpring/ # 框架核心代码
│ │ ├── anno/ # 自定义注解
│ │ │ ├── ioc/ # IoC 注解:@Component/@Service/@Repository/@PostConstruct
│ │ │ └── di/ # DI 注解:@Autowired/@Qualifier/@Primary/@Value
│ │ ├── context/ # 容器接口和实现:BeanFactory/ApplicationContext
│ │ ├── model/ # IoC/DI 执行器:IocExecutor/DiExecutor
│ │ └── utils/ # 工具集:AnnoUtils/ReflectUtils
│ ├── dao/ # 被容器托管的 DAO 组件
│ ├── service/ # 业务服务层
│ ├── pojo/ # XML 声明的示例 Bean
│ └── test/ # JUnit 4 集成测试
├── lib/ # 运行所需第三方依赖
└── bulid/build-compat.ps1 # PowerShell 编译打包脚本
重点包职责:
org.sradon.mySpring.context:容器抽象、ClassPathXmlApplicationContext实现org.sradon.mySpring.model:IoC/DI 主流程驱动器org.sradon.mySpring.utils:注解解析、反射注入、接口匹配策略
⚙️ 运行环境与依赖
- JDK:支持 8+(
bulid/build-compat.ps1可兼容 JDK 8 与 JDK 17) - 构建工具:无强制要求,可直接使用 IDE/脚本编译
- 关键依赖(位于
lib/)dom4j-1.6.1.jar、jaxen-1.1-beta-6.jar:解析applicationContext.xmlcommons-beanutils-1.7.0.jar、commons-logging-1.1.1.jar:执行属性赋值junit-4.8.2.jar:执行ContextTest- 如需
@Resource支持,请确保运行环境 classpath 中包含javax.annotation-api
可选:运行 bulid/build-compat.ps1 自动编译核心框架并生成 dist/myspring-framework-1.0.jar。
# 在仓库根目录执行
pwsh bulid/build-compat.ps1
🧠 IoC 容器实现要点
1. 注解定义
@Component:基座注解,允许提供自定义 Bean 名称@Service/@Repository:通过元注解继承@Component语义,实现语义化角色划分@PostConstruct:Bean 初始化后执行的钩子方法
相关源码:org.sradon.mySpring.anno.ioc.*
2. 扫描与注册流程
入口:ClassPathXmlApplicationContext#IOCbyAnno
- 解析
<component-scan base-package="..."/>,得到多包扫描清单 - 使用类加载器定位包对应的文件目录
IocExecutor.BeanLoader递归扫描.class文件AnnoUtils.isComponent判定是否属于 IoC 注解集合- 命名策略:优先注解
value→ 默认使用全限定类名 - 创建实例、执行
@PostConstruct方法、写入beans(容器)与defines(元数据索引)
核心代码摘录:
public static final Class<? extends Annotation>[] IOC_ANNOS =
new Class[]{Component.class, Service.class, Repository.class};
if (iocAnno != null) {
Object bean = clazz.newInstance();
AnnoUtils.useInit(clazz, bean); // 调用 @PostConstruct 方法
String key = resolveBeanKey(clazz, annotationValue);
putBean(beans, defines, key, bean, clazz);
}
3. XML Bean 注册
除了注解扫描,容器还支持 <bean> 手工申明:
<bean id="cvn" class="org.sradon.pojo.bean.CVN">
<property name="name" value="福建号"/>
<property name="fighter" ref="fighter"/>
</bean>
<bean id="fighter" class="org.sradon.pojo.bean.Fighter"/>
IOCbyXML 负责:
- 按
id/name或默认类名注册 Bean - 复用
IocExecutor.putBean与注解注册写入同一容器
💉 DI(依赖注入)实现详解
入口:ClassPathXmlApplicationContext#DIbyAnno
1. 支持的注入方式
@Autowired:按类型注入;若存在多个候选,结合@Qualifier/@Primary决定目标@Qualifier:强制指定 Bean 名称@Primary:为接口多实现场景指定默认实现@Resource(JSR-250):先按name,次按type,再退回接口匹配@Value:注入常量、集合(支持int[]、Integer[]、List<String>等)
2. 装配策略
AnnoUtils.putAutowired 关键逻辑:
@Qualifier→ 直接命名匹配- 容器中按类型(全限定名)查找
- 若未命中,
BeanDefine维护的接口 → 实现映射解决类型缺口 - 多实现冲突时触发
@Primary或抛出异常提示显式指定
if (f.isAnnotationPresent(Qualifier.class)) {
ReflectUtils.FieldSetter(f, obj, beans.get(qualifierValue));
return;
}
Object bean = beans.get(f.getType().getName());
if (bean == null) {
String beanKey = findInterface(defines, f, beans);
bean = beans.get(beanKey);
}
ReflectUtils.FieldSetter(f, obj, bean);
3. XML 属性注入
DIbyXML 读取 <property>:
ref:按名称从容器取对象value:常量赋值,使用BeanUtils完成基本类型转换
这样即可混合使用注解装配与 XML 属性注入。
🧩 核心组件说明
BeanFactory:容器顶层接口,定义getBean、listBeans等基础操作ApplicationContext:当前实现直接继承BeanFactoryClassPathXmlApplicationContext:- 维护
Map<String, Object> beans(实例表) - 维护
List<BeanDefine> defines(接口、父类索引) - 构造函数串联 IoC/DI 注解与 XML 流程
- 维护
BeanDefine:记录 Bean key、实现类、接口集合,为接口注入与@Primary决策提供数据ReflectUtils.FieldSetter:统一处理私有字段赋值,避免 setter 限制
🔍 配置文件剖析
src/applicationContext.xml 样例:
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<!-- 1. IoC 扫描范围 -->
<component-scan base-package="org.sradon.service, org.sradon.dao, org.sradon.pojo"/>
<!-- 2. XML Bean 定义示例 -->
<bean id="cvn" class="org.sradon.pojo.bean.CVN">
<property name="name" value="福建号"/>
<property name="fighter" ref="fighter"/>
</bean>
<bean id="fighter" class="org.sradon.pojo.bean.Fighter">
<property name="name" value="歼35"/>
</bean>
</beans>
可按需扩展 <component-scan> 列表,或直接追加 <bean> 标签支持手动注册。
🚀 使用与测试流程
1. 环境准备
- 保证
lib/中 JAR 已加入运行时 classpath - 若使用 IDE(IntelliJ IDEA / Eclipse),设置模块依赖指向
lib/ - 确保
javax.annotation-api在 classpath 内(运行@Resource时必需)
2. 运行容器示例
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
GoodsService goodsService = (GoodsService)
context.getBean("org.sradon.service.impl.GoodsServiceImpl");
GoodsService.getGoodsById();
context.listBeans(); // 查看容器中 Bean
context.listDefines(); // 查看 Bean 元信息
3. JUnit 测试流程
测试类:org.sradon.test.ContextTest
@Test
public void testAll() {
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
GoodsService goodsService = (GoodsService)
context.getBean("org.sradon.service.impl.GoodsServiceImpl");
TypesService typesService = (TypesService) context.getBean("TS");
goodsService.getGoodsById(); // 触发 @Qualifier("DDI3") 注入逻辑
typesService.testDoubleDAO(); // 演示 @Primary 的覆盖效果
System.out.println(context.getBean("cvn"));
System.out.println(context.getBean(Fighter.class));
}
运行方式:
- IDE 中右键
ContextTest#testAll - 或使用
java -cp "lib/*;dist/classes" org.junit.runner.JUnitCore org.sradon.test.ContextTest
4. 常见调试要点
- 容器启动日志
[INFO @ioc]、[SUCCESS]可快速定位 Bean 注册进度 context.listDefines()输出接口 → 实现映射,辅助排查多实现注入冲突- 若抛出
@Resource相关异常,优先确认javax.annotation-api.jar是否在 classpath 中
📊 容器初始化时序
✨ 技术亮点与扩展思路
- 设计模式融入:工厂模式(统一 Bean 创建)、策略模式(多注解处理)、单例容器(默认作用域)
- 接口优先策略:
BeanDefine维护接口实现列表,配合@Primary/@Qualifier解决多实现场景 - 生命周期钩子:支持
@PostConstruct自定义初始化逻辑 - 配置灵活性:注解 + XML 双通道,方便演示不同的配置方式
- 可扩展空间:
- 增加
BeanPostProcessor风格的扩展点 - 加入作用域管理(prototype/request 等)
- 扩展 AOP/事件发布等 Spring 功能
- 增加
📈 与 Spring Framework 的差异对比
| 能力点 | 本项目 | Spring 官方 |
|---|---|---|
| IoC/DI | ✅ 注解 + XML 基础实现 | ✅ 完整实现 |
| Bean 作用域 | ❌ 单例 | ✅ 单例/原型/会话等 |
| AOP/事务 | ❌ 未实现 | ✅ 全量支持 |
| 条件装配 | ❌ 未实现 | ✅ @Conditional、Profile |
| 配置方式 | ✅ XML + 注解 | ✅ XML + JavaConfig + Groovy |
| 生命周期 | ✅ @PostConstruct | ✅ BeanPostProcessor、Aware 接口等 |
🎯 总结与收获
- IoC 核心:通过包扫描、注解解析和反射创建 Bean,将对象管理统一交给容器
- DI 核心:多策略匹配 + 元数据索引,保证接口/实现解耦的同时完成依赖注入
- 容器设计:
Map保存实例、List保存定义,结构清晰、易于扩展 - 实战意义:帮助快速掌握 Spring 容器初始化流程,为阅读官方源码打下坚实基础
如果这个项目对你有启发,欢迎 Star 支持,也欢迎提出改进建议。
🌐 Gitee地址:Gitee
如果这篇文章对你有帮助,请点赞👍、收藏⭐、关注🔔!
更多推荐
所有评论(0)