手写一个简易Spring框架:实现自己的依赖注入容器
目录
1.引言
1.1介绍依赖注入(DI)和控制反转(IoC)的概念
控制反转(IoC)
传统方式:对象自己创建和管理依赖关系
IoC方式:将对象的创建和依赖关系的管理交给外部容器
核心思想:对象不主动寻找依赖,而是被动接收依赖注入
依赖注入(DI)
定义:一种实现IoC的具体方式
工作机制:容器在运行期动态地将依赖关系注入到组件中
三种注入方式:构造器注入 ,setter方法注入 , 字段注入(如本项目中的@AutoWried)
1.2 为什么需要自己实现一个简单的DI框架
学习目的:
- 深入理解Spring原理:通过实践掌握Spring框架核心机制
- 提升反射编程能力:熟练运用Java反射机制处理注解和类操作
- 理解设计模式:体会工厂模式、单例模式等在框架中的应用
实践价值:
- 轻量级解决方案:避免引入大型框架的复杂性
- 定制化需求:根据特定业务场景定制注入逻辑
- 性能优化:针对具体场景优化注入过程
1.3 本项目的实际应用场景
多数据源切换
- 可以通过添加不同的@Mapper标注实现类来支持多种数据库
- 如MySQLDao和GaussDAO可以共存,通过配置选择具体使用哪个
插件化架构
- 第三方可以通过实现标准接口并添加@Mapper注解来扩展功能
- 主程序无需修改代码即可集成新的实现
微服务解耦
- 服务间通过接口依赖,具体实现可以在运行时动态注入
- 便于测试和维护,降低模块间耦合度
2.核心设计思路
2.1 整体架构设计
三层架构模式:采用典型的分层设计,包括控制层(UserController)、接口层(Dao)和实现层 (MySQLDao、GaussDAO)、
控制层(UserController):
public class UserController {
@AutoWried
private Dao dao;
public void addUser(){
this.dao.add();
}
public void queryUser(){
this.dao.query();
}
}
接口层(Dao):
public interface Dao {
public void add();
public void query();
}
MySQLDao:
@Mapper
public class MySQLDao implements Dao{
public void add(){
System.out.println("MySQLDao.add()");
}
public void query(){
System.out.println("MySQLDao.query()");
}
}
GaussDAO:
@Mapper
public class GaussDAO implements Dao{
public void add(){
System.out.println("使用高斯数据库");
}
public void query(){
System.out.println("使用高斯数据库");
}
}
容器管理:通过BeanCollection类作为简易IoC容器,负责对象的创建和依赖管理
注解驱动:使用自定义注解实现声明式的依赖注入,降低代码耦合度
2.2关键注解的设计([@AutoWried]
@AutoWried 注解
作用范围:ElementType.FIELD,只能用于字段声明
生命周期:RetentionPolicy.RUNTIME,运行时可通过反射获取
功能:标记需要自动注入的依赖字段,如UserController中的dao字段
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoWried {
}
@Mapper 注解
作用范围:ElementType.TYPE,可用于类声明
生命周期:RetentionPolicy.RUNTIME,运行时有效
功能:标识具体的实现类,表明该类可以被注入到接口类型的字段中
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Mapper {
}
2.3 自动扫描和注入流程
BeanCollection:
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
public class BeanCollection {
public static Object createObject(Class c)throws ClassNotFoundException{
Object result = null;
List<String> javaLists = new ArrayList<String>();//用于存放java文件
try {
result = c.newInstance();//利用反射创建对象
Field[] fields = c.getDeclaredFields();//获取所有字段
//遍历字段
for (Field field : fields) {
if (field.isAnnotationPresent(AutoWried.class)) {
Class<?> autoClass = field.getType();//获取字段类型
if(autoClass.isInterface()){
//获取整个包路径,方便后续遍历
String pkPath = autoClass.getPackage().getName();//获取包名
String path = "E:\\ideaproject\\java\\Springboot\\src\\main\\java\\";
pkPath = path + pkPath.replace(".", "\\");
getChildFileLists(new File(pkPath), javaLists);
//遍历java文件
for (String javaList : javaLists) {
if(checkChildClass(javaList, Dao.class)){
Class clazz = Class.forName(javaList);
boolean flag1 = clazz.isAnnotationPresent(Mapper.class);
if (flag1) {
field.setAccessible(true);
field.set(result, clazz.newInstance());//设置属性值
}
}
}
}
}
}
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return result;
}
public static boolean checkChildClass(String className, Class<?> parentClass){
Class zc = null;
try {
zc = Class.forName(className);
if (Modifier.isAbstract(zc.getModifiers())) {
return false;
}
if (Modifier.isInterface(zc.getModifiers())) {
return false;
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 判断是不是这个接口子类对象
return parentClass.isAssignableFrom(zc);
}
public static void getChildFileLists(File file, List<String> lists){
File[] fs = file.listFiles();//获取当前目录下的所有文件
for (File f : fs) {
String filePath = f.getPath();
if (filePath.endsWith(".java")) {
String cname1 = filePath.substring(filePath.indexOf("java") + 5, filePath.length());
cname1 = cname1.substring(0, cname1.lastIndexOf(".java"));
cname1 = cname1.replaceAll("\\\\", ".");
if (cname1.startsWith("Springboot.src.main.java.")) {
cname1 = cname1.substring("Springboot.src.main.java.".length());
}
lists.add(cname1);
}
}
}
}
- 对象创建阶段:BeanCollection.createObject()方法通过反射创建目标类实例
- 依赖扫描阶段:遍历目标类的所有字段,查找带有@AutoWried注解的字段
- 类型匹配阶段:获取字段声明的类型(如Dao接口),确定需要注入的依赖类型
- 类路径扫描:根据接口所在包路径,扫描该路径下的所有Java类文件
- 实现类筛选:通过checkChildClass()方法验证类是否实现了目标接口且带有@Mapper注解
- 依赖注入:创建符合条件的实现类实例,通过反射设置到目标字段中
3.核心代码解析
3.1 [BeanCollection]类的实现细节
- 对象创建入口:createObject(Class c)方法作为核心入口,负责实例化目标类并完成依赖注入
- 字段扫描机制:通过getDeclaredFields()获取类的所有声明字段,逐一检查是否存在@AutoWried注解
- 依赖解析策略:针对接口类型字段,扫描实现类并验证@Mapper注解,确保注入正确的实现
3.2 反射机制的应用
- 实例化对象:使用Class.newInstance()创建类实例,触发无参构造函数
- 字段访问控制:通过field.setAccessible(true)突破私有字段访问限制
- 动态赋值操作:利用field.set(result, clazz.newInstance())将实现类实例注入到目标字段
- 注解检测:通过isAnnotationPresent()方法判断字段或类是否携带特定注解
3.3类路径扫描逻辑
- 包路径定位:从接口类型获取包名,转换为本地文件系统路径进行扫描
- 文件遍历处理:使用listFiles()递归获取目录下所有.java文件
- 类名提取规则:从文件路径中截取相对路径部分,转换为标准的类全限定名格式
- 继承关系验证:通过isAssignableFrom()方法确认类与接口间的实现关系,排除抽象类和接口本身
4.运行效果展示
4.1测试代码构成
public class Test {
public static void main(String[] args){
UserController uc = null;
try {
uc = (UserController) BeanCollection.createObject(UserController.class);
uc.addUser();
uc.queryUser();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
4.2输出结果
MySQLDao.add()
MySQLDao.query()
更多推荐
所有评论(0)