当前位置: 首页 > news >正文

mini-spring实现

一、简介

基于bilibili up主“学java的生生”的手写spring教程,实现一个简单的spring框架。mini-spring包含的核心功能有:包扫描,BeanDefinition封装,IOC容器,依赖注入,生命周期管理,按类型/名称获取Bean等功能。后续还会继续基于mini-spring实现mvc。

二、项目框架

mini-spring
├── .idea
├── .mvn
└── src└── main├── java│   └── tech│       └── insight│           └── minispring│               ├── annotation│               │   ├── Autowired│               │   ├── Component│               │   └── PostConstruct│               ├── entity│               ├── ApplicationContext│               ├── BeanDefinition│               ├── BeanPostProcessor│               ├── Main│               └── MyBeanPostProcessor└── resources└── application.properties

三、核心代码解析

1.BeanDefinition

package tech.insight.minispring;import tech.insight.minispring.sub.Autowired;
import tech.insight.minispring.sub.PostConstruct;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;public class BeanDefinition {private final String name;private final Constructor<?> constructor;private final Method postConstructMethod;private final List<Field> autowiredFields;private final Class<?> beanType;public BeanDefinition(Class<?> type){this.beanType = type;Component component = type.getDeclaredAnnotation(Component.class);this.name = component.name().isEmpty() ? type.getSimpleName() : component.name();try {this.constructor = type.getConstructor();this.postConstructMethod = Arrays.stream(type.getDeclaredMethods()).filter(m -> m.isAnnotationPresent(PostConstruct.class)).findFirst().orElse(null);this.autowiredFields = Arrays.stream(type.getDeclaredFields()).filter(m -> m.isAnnotationPresent(Autowired.class)).toList();} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}public String getName(){return name;}public Constructor<?> getConstructor(){return constructor;}public Method getPostConstructMethod(){return postConstructMethod;}public List<Field> getAutowiredFields(){return autowiredFields;}public Class<?> getBeanType(){return beanType;}
}

为什么要定义BeanDefinition,而不是直接定义Bean然后进行实例化?

BeanDefinition存储的是Bean的元信息,用来描述怎么创建一个Bean。BeanDefinition像是配方,而Bean是具体做出来的菜。

使用BeanDefinition有以下优势:

  1. 延迟实例化(Lazy Init)

如果你直接new,那一启动就得把所有对象都创建出来。而通过BeanDefinition,可以先将Bean的配方存储,在真正需要使用时再创建Bean,这样就能控制实例化的时机。

  1. 生命周期管理

直接使用new,只能拿到对象本身,无法管理对象的生命周期。因为BeanDefinition记录了Bean的元数据,所以可以在合适的时机执行生命周期方法,比如PostConstruct,PreDestory等。

  1. 依赖注入

BeanDefinition中保存了类中带有@Autowired的字段,创建Bean时能够通过反射注入对应的依赖对象。

  1. 代理增强

可以通过BeanDefinition的配置,决定返回一个原始对象还是增强后的代理对象,可以额外实现日志、事务等功能,这也是SpringAOP的基础。

2.ApplicationContext

  1. 包扫描函数
*** 包扫描函数* @param packageName* @return*/
public List<Class<?>> scanPackage(String packageName) throws Exception {List<Class<?>> classList = new ArrayList<>();// 需要a.b.cURL resource = this.getClass().getClassLoader().getResource(packageName.replace(".", File.separator));Path path = Paths.get(resource.toURI());Files.walkFileTree(path,new SimpleFileVisitor<>(){@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {Path absolutePath = file.toAbsolutePath();if (absolutePath.toString().endsWith(".class")) {String replaceStr = absolutePath.toString().replace(File.separator, ".");int index = replaceStr.indexOf(packageName);String className = replaceStr.substring(index,replaceStr.length() - ".class".length());try {classList.add(Class.forName(className));} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}return FileVisitResult.CONTINUE;}});return classList;

resource:URL对象,包所在的文件目录的定位地址

URI:更通用的统一资源标识符,避免URL中的特殊符号产生的起义

Path:NIO引入的新文件系统的抽象,比File更强大,能够把URI转化为操作系统本地文件路径

Files.walkFileTree():递归遍历包目录及其子目录

这一个方法其实是访问者模式的体现:把堆某种结构的访问操作,从结构本身分离出来,交给访问者去完成。在不改变数据结构的前提下,灵活扩展对结构的操作。

在这里文件是被访问者,SimpleFileVisitor子类是访问者,visitFile()是访问者逻辑,Files.walkFileTree是调度器,遍历目录将文件交给visitor处理。

visitFile()的逻辑是将后缀为.class的文件的绝对路径转化为类的全限定类名,然后通过全限定类名使用反射的方式加载对象(并不是实例化对象,只是生成字节码文件,存储对象的元信息),最后加入classList中。classList中存储的是对象的元信息,之后会被封装成BeanDefinition。

  1. wrapper映射函数:将字节码对象封装为BeanDefinition
protected BeanDefinition wrapper(Class<?> type){// 1. 用 Class<?> 创建一个 BeanDefinition(即“配方”)BeanDefinition beanDefinition = new BeanDefinition(type);// 2. 校验:容器里不能有重复的 beanNameif (beanDefinitionsMap.containsKey(beanDefinition.getName())){throw new RuntimeException("Duplicate bean name: " + beanDefinition.getName());}// 3. 注册:把 BeanDefinition 放进 beanDefinitionsMap(配方容器)beanDefinitionsMap.put(beanDefinition.getName(), beanDefinition);// 4. 返回 BeanDefinition(有时候可能还要进一步处理)return beanDefinition;
}
  1. createBean&&doCreateBean
protected Object createBean(BeanDefinition beanDefinition) {String name = beanDefinition.getName();if (ioc.containsKey(name)){return ioc.get(name);}if (loadingIoc.containsKey(name)){return loadingIoc.get(name);}return doCreateBean(beanDefinition);
}

先查询单例池IOC中是否有bean,如果有说明已经创建过了,直接返回。再检查loadingIoc中是否有正在创建的bean,如果有,先返回半成品用于解决循环依赖。如果两个容器中都没有,则进行doCreateBean()。

private Object doCreateBean(BeanDefinition beanDefinition) {Constructor constructor = beanDefinition.getConstructor();Object bean = null;try {// 1. 调用构造器,实例化对象,得到一个裸对象bean = constructor.newInstance();// 2. 放入临时集合,标记“正在创建”loadingIoc.put(beanDefinition.getName(), bean);// 3. 自动注入依赖(相当于 Spring 的依赖注入阶段)autowiredBean(bean, beanDefinition);// 4. 初始化(调用初始化方法、执行 Aware 接口、BeanPostProcessor 等)bean = initializeBean(bean, beanDefinition);// 5. 创建完成,从 loadingIoc 移除loadingIoc.remove(beanDefinition.getName());// 6. 放入单例池 ioc,表示 Bean 已经创建完成ioc.put(beanDefinition.getName(), bean);} catch (Exception e){throw new RuntimeException(e);}return bean;

Spring三级缓存解决循环依赖

在Spring的DefaultSingletonBeanRegistry中有三个Map:

1)singletonObjects:相当于ioc单例池,存放已经初始化好的bean。

2)earlySingletonObjects:相当于loadingIoc,存放已实例化但还未初始化完成的bean。

3)singletonFactories:存放ObjectFactory,一个生成bean的工厂对象

Spring三级缓存相较于本文代码的优势是:在循环依赖时,B拿到的A是工厂返回的代理对象,既可以返回原始对象,也能返回增强后的代理对象;而本文的实现是直接返回裸对象(使用BeanDefinition的构造器进行创建对象),在创建对象后无法再进行增强。因此,Spring的三级缓存具有更强大,更灵活的AOP功能。

图论环检测角度解决循环依赖

提供一个另外思考的视角,Spring 解决循环依赖的做法是 【图论环检测】 的经典实践。 依赖关系可以表示为一个有向图,Cat -> Dog -> Cat 的依赖关系可以表示为一个【有向图】 ,Bean是怕【顶点】, 依赖关系是【边】。Spring在createBean时会递归构建依赖图 (深度优先遍历DFS) , 如果发现当前路径中某个Bean正在创建中(即存在“正在创建”的顶点被重复访问),则判定存在环 【环检测】 。简化做法是采用二级缓存来获取 Cat 的引用 【在图中引入一个虚拟顶点】, 将环拆解为链式依赖 ,从而避免了无限递归。当所有依赖注入完成,再将真实顶点替换虚拟顶点。故视频中的loadingIoc缓存是用于存放虚拟顶点进行环检测的集合容器,ioc是存放真实顶点的集合容器。

  1. autowiredBean 自动注入方法
private void autowiredBean(Object bean, BeanDefinition beanDefinition) throws IllegalAccessException {for (Field autowiredField : beanDefinition.getAutowiredFields()) {autowiredField.setAccessible(true);autowiredField.set(bean,getBean(autowiredField.getType()));}
}
  1. BeanPostProcessor
private Object initializeBean(Object bean,BeanDefinition beanDefinition) throws InvocationTargetException, IllegalAccessException {for (BeanPostProcessor postProcessor : postProcessors) {postProcessor.beforeInitializeBean(bean,beanDefinition.getName());}Method postConstructMethod = beanDefinition.getPostConstructMethod();if (postConstructMethod != null){postConstructMethod.invoke(bean);}for (BeanPostProcessor postProcessor : postProcessors) {postProcessor.afterInitializeBean(bean,beanDefinition.getName());}return bean;
}
/*** 将实现BeanPostProcessor接口的Bean实例化并放入postProcessor列表*/
private void initBeanPostProcessor() {beanDefinitionsMap.values().stream().filter(bd ->BeanPostProcessor.class.isAssignableFrom(bd.getBeanType())).map(this::createBean).map((bean) -> (BeanPostProcessor)bean).forEach(postProcessors::add);
}

@PostConstruct:在依赖注入完成后,Bean可用前的初始化钩子。

PostProcessor:处理器。所有实现BeanPostProcessor接口的Bean都会被注册成为一个处理器。所有postProcessors列表中的处理器都可以参与生命周期的增强。在创建新的bean时调用initializeBean方法,执行处理器中的逻辑:

先执行 MyBeanLogger.beforeInitializeBean()

再执行 @PostConstruct 方法(如果有)

最后执行 MyBeanLogger.afterInitializeBean()

所以postProcessor的作用就是:定义一系列处理器,在自动注入完成后,对半成品bean进行生命周期的增强

  1. getBean
/*** 通过名字获取对象* @param name* @return*/
public Object getBean(String name) {if (name == null) {return null;}Object bean = ioc.get(name);//如果bean不为空,直接返回if (bean != null){return bean;}//如果map中有但还未初始化,则将其初始化后返回//这样做是为了确保map中存在beanDefinition但还未在ioc容器中创建的对象也被创建if (beanDefinitionsMap.containsKey(name)){return createBean(beanDefinitionsMap.get(name));}//ioc中确实没有return null;
}/*** 通过类型获取对象* @param beanType* @return* @param <T>*/
public <T> T getBean(Class<T> beanType){String beanName = this.beanDefinitionsMap.values().stream().filter(bd -> beanType.isAssignableFrom(bd.getBeanType())).map(BeanDefinition::getName).findFirst().orElse(null);return (T)getBean(beanName);
}/**** @param beanType* @return* @param <T>*/
public <T> List<T> getBeans(Class<T> beanType){return this.beanDefinitionsMap.values().stream().filter(bd -> beanType.isAssignableFrom(bd.getBeanType())).map(BeanDefinition::getName).map(this::getBean).map(bean -> (T)bean).toList();
}

Spring在通过类型获取bean时,为什么需要通过遍历IoC容器获取,而不是通过维护一个Map<Class,Bean>来获取?

因为要支持多态、代理、多实现的场景。如果传入一个接口或一个父类,其具有多个实现,就不能保证Class和Bean的一一映射关系,无法准确获取bean。因此在通过名称获取时依靠beanNname -> Bean的唯一映射,通过Map来获取;而在通过类型获取时,需要遍历ioc容器获取bean,再匹配类型,以支持多态、代理、多实现的场景。

http://www.hskmm.com/?act=detail&tid=23540

相关文章:

  • 10.3
  • Linux 代码利用 STDOUT 打印日志导致应用“假死”?一次线上 Bug 的深度排查与解决
  • 2025 年地坪研磨机公司推荐榜单:盘点 TOP 品牌的格力,宁德时代等标杆客户合作案例
  • Python 新手入门:从零开始学习 Python 的 10 个关键步骤
  • EPL S22 Stage 2 赛前前瞻
  • 计算机类毕业设计开题报告注意事项 - 教程
  • 2025山东设备回收公司 TOP 交易服务推荐排行榜,济宁,梁山设备回收,二手,饮料,食品,制药,实验室,生产线,化工厂,废旧,大型,专业设备回收公司推荐
  • 2025饮料设备回收公司 TOP 交易服务推荐排行榜,济宁,梁山饮料设备回收果汁饮料整厂,饮料生产线,碳酸饮料,乳制品,杀菌机,果汁饮料,二手灌装机,果汁设备回收公司推荐
  • 压力位和支撑位概念
  • 2025 年浙江义乌匹克球拍厂家 TOP 企业品牌推荐排行榜,碳纤维,高级,轻质,定制,高定,比赛专用,玻璃纤维,木制,儿童,匹克球拍套装公司推荐
  • 实用指南:k8s中的schedule
  • 【光照】[PBR][环境光]实现方法解析
  • 树莓派搭建NAS之五:数据同步
  • 初识文件管理
  • 微信社群机器人搭建 教程/开发
  • 2025 年激光粒度仪厂家 TOP 企业品牌推荐排行榜,电位仪 / 纳米粒度及 Zeta 电位仪 / Zeta 电位仪公司推荐
  • 微信智能机器人开发-基于WTAPI框架,实现强大的个微管理
  • 2025粒度仪厂家 TOP 企业品牌推荐排行榜,粒度分析仪,喷雾,激光,纳米,在线,图像粒形,干湿两用粒度仪公司推荐
  • glibc堆
  • 完整教程:从 “T+1” 到 “秒级”:MySQL+Flink+Doris 构建实时数据分析全链路
  • 平均数
  • 质数表
  • 小作业 11
  • 深入解析:【RabbitMQ】原理解析
  • 一次insert插入多条数据比insert循环插入数据效率高多少?
  • 内存映射文件
  • 详细介绍:Java安全“幽灵”:深入剖析内存马的原理、注入与查杀
  • 2025波形护栏厂家 TOP 企业品牌推荐排行榜,山东波形护栏防撞,三波,二波,双波,喷塑,公路,热浸锌,浸塑,镀锌波形护栏公司推荐!
  • 好数
  • 2025.10 做题记录