🌱 手写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.jarjaxen-1.1-beta-6.jar:解析 applicationContext.xml
    • commons-beanutils-1.7.0.jarcommons-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

  1. 解析 <component-scan base-package="..."/>,得到多包扫描清单
  2. 使用类加载器定位包对应的文件目录
  3. IocExecutor.BeanLoader 递归扫描 .class 文件
  4. AnnoUtils.isComponent 判定是否属于 IoC 注解集合
  5. 命名策略:优先注解 value → 默认使用全限定类名
  6. 创建实例、执行 @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 关键逻辑:

  1. @Qualifier → 直接命名匹配
  2. 容器中按类型(全限定名)查找
  3. 若未命中,BeanDefine 维护的接口 → 实现映射解决类型缺口
  4. 多实现冲突时触发 @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:容器顶层接口,定义 getBeanlistBeans 等基础操作
  • ApplicationContext:当前实现直接继承 BeanFactory
  • ClassPathXmlApplicationContext
    • 维护 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. 环境准备

  1. 保证 lib/ 中 JAR 已加入运行时 classpath
  2. 若使用 IDE(IntelliJ IDEA / Eclipse),设置模块依赖指向 lib/
  3. 确保 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));
}

运行方式:

  1. IDE 中右键 ContextTest#testAll
  2. 或使用 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 中

📊 容器初始化时序

Client ClassPathXmlApplicationContext IocExecutor DiExecutor AnnoUtils new ClassPathXmlApplicationContext() 解析 applicationContext.xml IOCbyAnno() 判定 IOC 注解 注册 Bean + BeanDefine IOCbyXML() DIbyAnno() 注入字段 DIbyXML() 容器初始化完成 Client ClassPathXmlApplicationContext IocExecutor DiExecutor AnnoUtils

✨ 技术亮点与扩展思路

  • 设计模式融入:工厂模式(统一 Bean 创建)、策略模式(多注解处理)、单例容器(默认作用域)
  • 接口优先策略BeanDefine 维护接口实现列表,配合 @Primary/@Qualifier 解决多实现场景
  • 生命周期钩子:支持 @PostConstruct 自定义初始化逻辑
  • 配置灵活性:注解 + XML 双通道,方便演示不同的配置方式
  • 可扩展空间
    1. 增加 BeanPostProcessor 风格的扩展点
    2. 加入作用域管理(prototype/request 等)
    3. 扩展 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


如果这篇文章对你有帮助,请点赞👍、收藏⭐、关注🔔!

更多推荐