mini-spring实现

news/2025/10/3 13:38:54/文章来源:https://www.cnblogs.com/lumosdong/p/19124560

一、简介

基于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.mzph.cn/news/925957.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PML(Perfect Match Layer)介绍 - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

手机自助网站建设企业门户是什么

接着这一问题衍生出来的问题 arcgis的MapServer服务查询出来的结果geometry坐标点带*的问题-CSDN博客 个人感觉像是server版本的问题&#xff0c;具体不清楚&#xff0c;pg数据库里面的shape点集合坐标点的精度是8&#xff0c;但是server服务查出来的默认都十几位。所以存在一…

揭阳装修网站建设数字资产交易网站建设

说明&#xff1a; 《火球——UML大战需求分析》是我撰写的一本关于需求分析及UML方面的书&#xff0c;我将会在CSDN上为大家分享前面几章的内容&#xff0c;总字数在几万以上&#xff0c;图片有数十张。欢迎你按文章的序号顺序阅读&#xff0c;谢谢&#xff01;本书已经在各大网…

利用STM32CubeMX创建新的工程,使用vscode进行编码和调试

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

网站开发网站维护这行业怎么样wordpress 获取相关文章

来源&#xff1a;AI中国大脑的进化进程持续已久&#xff0c;从5亿年前的蠕虫大脑到现如今各种现代结构。例如&#xff0c;人类的大脑可以完成各种各样的活动&#xff0c;其中许多活动都是毫不费力的。例如&#xff0c;分辨一个视觉场景中是否包含动物或建筑物对我们来说是微不足…

龙岗爱联网站建设app下载app开发公司

第一步 进入百度地图开发平台 百度地图开放平台 | 百度地图API SDK | 地图开发 第二步注册 获取AK秘钥&#xff0c;点击【创建应用】进入AK申请页面&#xff0c;填写应用名称&#xff0c;务必选择AK类型为“浏览器端”&#xff0c;JS API只支持浏览器端AK进行请求与访问 下面…

四川宜宾建设局官方网站曼朗策划网站建设

1.概述 在12.0的系统rom产品定制化开发中,在一些产品核心开发中,第三方app需要开启系统通知权限,然后可以在app中,监听系统所有通知,来做个通知中心的功能,所以需要授权 获取系统通知的权限,然后来顺利的监听系统通知。来做系统通知的功能,首选分析下相关授权通知的功…

DevEco Studio模拟器的采用

DevEco Studio模拟器的采用pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", …

微信网站模版wordpress主题加授权方式

自从电动汽车GB/T32960标准颁布&#xff0c;要求所有电动汽车必须上传数据开始&#xff0c;各车厂就开始花费大量的人力物力&#xff0c;用于数据的上传与存储。同时随着智能化、网联化的趋势&#xff0c;不断丰富上传数据的内容与数量。数据已成为车厂的重要资产&#xff0c;但…

公司网站建设一般多少钱网站通内容管理系统

SPSSAU共提供两种文本聚类方式&#xff0c;分别是按词聚类和按行聚类。按词聚类是指将需要分析的关键词进行聚类分析&#xff0c;并且进行可视化展示&#xff0c;即针对关键词进行聚类&#xff0c;此处关键词可以自由选择。按行聚类分析是指针对以‘行’为单位进行聚类分析&…

怎么用wordpress修改网站源码网站建设违约合同

本期复刻效果&#xff1a; 感觉出的聚类分析树状图绘制工具也不少了&#xff0c;未来可能会统一整理为一个工具包&#xff1f;(任重道远&#xff0c;道阻且长)&#xff1a; 代码讲解 0 数据设置 写了比较多的注释应该比较易懂&#xff1a; clc; clear; close all% 样品起名s…

实用指南:基于I.MX6ULL的Linux C多线程物联网网关+STM32+Qt上位机+Linux C++多线程服务器(含web)的多种无线通信系统的智慧农场

实用指南:基于I.MX6ULL的Linux C多线程物联网网关+STM32+Qt上位机+Linux C++多线程服务器(含web)的多种无线通信系统的智慧农场2025-10-03 13:21 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !imp…

网站建设制作与运营wordpress模板制作软件

使用Proteus仿真软件设计一个开机登录程序,单片机选用ATmegga48. 基础要求: 1.程序启动后在LCD1602液晶屏上提示用户通过独立按键输入密码(6位)。 2.密码输入错误则在屏幕上提示密码错误,密码输入正确则在屏幕上提示密 码正确后等待约3秒后进入主界面,在屏幕中央显示HelloWorld…

康桥网站建设html做一个登录注册页面

看到的实用资料记录网址&#xff1a; 1、技术学习网站&#xff1a; http://www.ixpub.net/forum.php 2、禅道项目管理软件(ZenTaoPMS)是一款国产的&#xff0c;基于LGPL协议&#xff0c;开源免费的项目管理软件&#xff0c;它集产品管理、项目管理、测试管理于一体&#xff0c;…

英雄联盟视频网站源码免费稳定的云服务器

专栏主页&#xff1a;计算机专业基础知识总结&#xff08;适用于期末复习考研刷题求职面试&#xff09;系列文章https://blog.csdn.net/seeker1994/category_12585732.html 题目描述 要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句。不能用循…

pthread_create详解:打开多线程编程的大门 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

做模具行业的网站wordpress怎么找模板

Tags : jstack日志发表时间&#xff1a;2019-03-17 23:53:19在分析线上问题时常使用到jstack 命令将当时Java应用程序的线程堆栈dump出来。面对jstack 日志&#xff0c;我们如何查看&#xff1f;首先要清楚线程的状态线程的状态有&#xff1a;new、runnable、running、waiting、…

唐山制作网站软件Wordpress点金

项目结构创建&提交到码云 数据库初始化 保持docker数据库一直打开 docker update redis --restartalways 连不上了&#xff0c;发现配置文件错了 换了一个配置文件。 快速开发 使用开源的脚手架 人人开源 (gitee.com) 使用renren-fast作为后台开发&#xff0c;使用…

南京网站设计 联络南京乐识广州建站平台哪家好

文章目录 递归与树的深度优先搜索&#xff1a;探索它们之间的关系递归的基本概念树的深度优先搜索递归与树的深度优先搜索的关系反转链表合并两个有序链表 总结 递归与树的深度优先搜索&#xff1a;探索它们之间的关系 递归是一种强大而优雅的编程技术,它允许我们通过将问题分…

网站地图怎么做html百度助手安卓版下载

场景 按规定尽可能减少开放到外网的端口&#xff0c;所以需要将多个服务部署到一个ip一个端口上。 方案 使用ng实现请求转发。根据http请求中的host与ng配置文件中的server_name匹配&#xff0c;转发到对应的机器上。 在docker上部署三个容器&#xff0c;每个容器中启动一个…