【手写模拟Spring底层原理】
文章目录
- 模拟Spring底层详解
- 1、结合配置类,扫描类资源
- 1.1、创建需要扫描的配置类AppConfig,如下:
- 1.2、创建Spring容器对象LyfApplicationContext,如下
- 1.3、Spring容器对象LyfApplicationContext扫描资源
- 2、结合上一步的扫描,遍历其Map集合,创建对象
- 3、创建对象后,需要提供需要获取Bean的方法
- 4、总结
模拟Spring底层详解
前置准备:创建部分注解,具体如下
/*** 依赖注入注解信息*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}/*** 自定义一个注解是为了标识==>使用此注解之处的类资源需要交给Spring容器管理* 可自定义beanName*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {String value() default "";
}/*** 定于扫描类(bean)资源路径*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {String value() default "";
}@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {String value() default "";
}
两个service类,用于测试:
@Component
@Scope()
public class OrderService {
}
@Component
public class UserService {private String name;@Autowiredprivate OrderService orderService;public void testDemo(){System.out.println("Spring 创建 userService 实例成功");System.out.println("Spring 依赖注入 orderService 实例对象:"+orderService);}
}
一个测试类:
public class TestSpringDemo {public static void main(String[] args) throws Exception{LyfApplicationContext context = new LyfApplicationContext(AppConfig.class);UserService userService = (UserService) context.getBean("userService");userService.testDemo();}
}
1、结合配置类,扫描类资源
Spring在创建对象前,需要去扫描,确定需要交给Spring管理的类资源,具体的实现步骤模拟代码如下:
1.1、创建需要扫描的配置类AppConfig,如下:
/*** 这个类主要是用于定义扫描资源的路径信息*/
@ComponentScan("com.practice.service")
public class AppConfig {
}
1.2、创建Spring容器对象LyfApplicationContext,如下
/*** 这个类主要是用于模拟Spring容器的(bean的创建与获取bean等)*/
public class LyfApplicationContext {public LyfApplicationContext(Class config) throws Exception{}}
1.3、Spring容器对象LyfApplicationContext扫描资源
在LyfApplicationContext容器含参构造中,需要结合传入扫描资源的配置类AppConfig,对资源进行扫描,具体实现代码如下:
/*** 这个类主要是用于模拟Spring容器的(bean的创建与获取bean等)*/
public class LyfApplicationContext {public LyfApplicationContext(Class config) throws Exception{//判断传入的config类上是否有componentScan注解if (config.isAnnotationPresent(ComponentScan.class)) {//1、获取传入类上的注解ComponentScan信息==>主要是获取扫描路径ComponentScan componentScanAnnotation = (ComponentScan) config.getAnnotation(ComponentScan.class);//2、获取注解中的值String path = componentScanAnnotation.value();//3、将注解中的值"."换为"/"path = path.replace(".","/");//4、结合当前容器的类加载器,加载路径path下的class资源//4.1 先获取当前容器的类加载器ClassLoader classLoader = LyfApplicationContext.class.getClassLoader();//4.2 利用上一步获取的类加载器获取path路径下的class文件资源url信息URL resource = classLoader.getResource(path); //D:/JavaWorkSpace/2023/spring/spring-study-demo01/target/classes/com/practice/service//4.3 获取当前resource路径下的文件资源信息File file = new File(resource.getFile());//4.4 遍历file文件数据,获取file下的所有class文件资源if (file.isDirectory()) {for (File f : file.listFiles()) {String absolutePath = f.getAbsolutePath(); //获取到class资源的绝对路径信息==>目的是为了加载此类信息// 4.4.1 将此类的绝对路径做处理,截取一部分absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class")).replace("\\",".");//com.practice.service.UserService//4.4.2 用上述得到的类加载器加载上述的absolutePath路径下的class资源信息==>目的是为了检查当前遍历的class资源上是否包含@Component注解try{Class<?> clazz = classLoader.loadClass(absolutePath);// 如果当前的对象实现了beanPostProcessor接口,需要将其加入beanPostProcessorList集合中if (BeanPostProcessor.class.isAssignableFrom(clazz)) {BeanPostProcessor instance = (BeanPostProcessor) clazz.getConstructor().newInstance();beanPostProcessorList.add(instance);}if (clazz.isAnnotationPresent(Component.class)) {//创建一个BeanDefinition对象,用于保存每个类的特征BeanDefinition beanDefinition = new BeanDefinition();beanDefinition.setType(clazz);//4.4.2.1 获取当前注解Component的值==>之定义的beanNameComponent annotation = clazz.getAnnotation(Component.class);String beanName = annotation.value();//如果当前传入的beanName为空的话==>利用默认的,也就是类名首字母小写作为beanNameif ("".equals(beanName)) {beanName = Introspector.decapitalize(clazz.getSimpleName());}//4.4.2.2 除此之外,还需要判断是否有Scope注解===>主要是判断当前的bean是否是单例或者是原型的if (clazz.isAnnotationPresent(Scope.class)) {//获取注解中的值Scope scopeAnnotation = clazz.getAnnotation(Scope.class);String value = scopeAnnotation.value();beanDefinition.setScope(value);}else {beanDefinition.setScope("singleton");}//将封装好的beanDefinition缓存到Map中beanDefinitionMap.put(beanName,beanDefinition);}}catch (Exception e){throw new Exception(absolutePath + "类加载失败",e);}}}}else {throw new Exception("缺少路径资源配置信息~~~");}}
}
其过程如下:
先结合传入扫描资源的配置类AppConfig,类上是否包含注解@ComponentScan,若包含注解,需要获取其注解中的参数信息(配置的扫描包路径),获取当前资源的类加载器,目的是为了获取target包下的class资源信息,获取到指定包路径下的class资源,利用其构造方法,创建对象,对对象中的属性以及对象上加入的注解信息进行遍历扫描,进行相关的逻辑处理,将其类元信息加入到BeanDefinition对象中,再将其封装为一个Map对象,在接下来的对象创建与获取的过程中做好基奠,其对象信息就是记录每一个类的特征,部分代码如下:
/*** 这个类主要是去记录下描述一个bean的特征*/
public class BeanDefinition {//类的类型private Class type;//创建类的方式==>单例还是原型等private String scope;public Class getType() {return type;}public void setType(Class type) {this.type = type;}public String getScope() {return scope;}public void setScope(String scope) {this.scope = scope;}
}
2、结合上一步的扫描,遍历其Map集合,创建对象
/*** 这个类主要是用于模拟Spring容器的(bean的创建与获取bean等)*/
public class LyfApplicationContext {public LyfApplicationContext(Class config) throws Exception{//判断传入的config类上是否有componentScan注解if (config.isAnnotationPresent(ComponentScan.class)) {//1、获取传入类上的注解ComponentScan信息==>主要是获取扫描路径ComponentScan componentScanAnnotation = (ComponentScan) config.getAnnotation(ComponentScan.class);//2、获取注解中的值String path = componentScanAnnotation.value();//3、将注解中的值"."换为"/"path = path.replace(".","/");//4、结合当前容器的类加载器,加载路径path下的class资源//4.1 先获取当前容器的类加载器ClassLoader classLoader = LyfApplicationContext.class.getClassLoader();//4.2 利用上一步获取的类加载器获取path路径下的class文件资源url信息URL resource = classLoader.getResource(path); //D:/JavaWorkSpace/2023/spring/spring-study-demo01/target/classes/com/practice/service//4.3 获取当前resource路径下的文件资源信息File file = new File(resource.getFile());//4.4 遍历file文件数据,获取file下的所有class文件资源if (file.isDirectory()) {for (File f : file.listFiles()) {String absolutePath = f.getAbsolutePath(); //获取到class资源的绝对路径信息==>目的是为了加载此类信息// 4.4.1 将此类的绝对路径做处理,截取一部分absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class")).replace("\\",".");//com.practice.service.UserService//4.4.2 用上述得到的类加载器加载上述的absolutePath路径下的class资源信息==>目的是为了检查当前遍历的class资源上是否包含@Component注解try{Class<?> clazz = classLoader.loadClass(absolutePath);// 如果当前的对象实现了beanPostProcessor接口,需要将其加入beanPostProcessorList集合中if (BeanPostProcessor.class.isAssignableFrom(clazz)) {BeanPostProcessor instance = (BeanPostProcessor) clazz.getConstructor().newInstance();beanPostProcessorList.add(instance);}if (clazz.isAnnotationPresent(Component.class)) {//创建一个BeanDefinition对象,用于保存每个类的特征BeanDefinition beanDefinition = new BeanDefinition();beanDefinition.setType(clazz);//4.4.2.1 获取当前注解Component的值==>之定义的beanNameComponent annotation = clazz.getAnnotation(Component.class);String beanName = annotation.value();//如果当前传入的beanName为空的话==>利用默认的,也就是类名首字母小写作为beanNameif ("".equals(beanName)) {beanName = Introspector.decapitalize(clazz.getSimpleName());}//4.4.2.2 除此之外,还需要判断是否有Scope注解===>主要是判断当前的bean是否是单例或者是原型的if (clazz.isAnnotationPresent(Scope.class)) {//获取注解中的值Scope scopeAnnotation = clazz.getAnnotation(Scope.class);String value = scopeAnnotation.value();beanDefinition.setScope(value);}else {beanDefinition.setScope("singleton");}//将封装好的beanDefinition缓存到Map中beanDefinitionMap.put(beanName,beanDefinition);}}catch (Exception e){throw new Exception(absolutePath + "类加载失败",e);}}}}else {throw new Exception("缺少路径资源配置信息~~~");}}//创建对象for (Map.Entry<String, BeanDefinition> definitionEntry : beanDefinitionMap.entrySet()) {//获取BeanDefinitionMap中的key和valueString beanName = definitionEntry.getKey();BeanDefinition definition = definitionEntry.getValue();//判断当前的BeanDefinition对象是否是单例if ("singleton".equals(definition.getScope())) {Object bean = creatBean(beanName, definition);singletonMap.put(beanName,bean);}}/*** 创建bean对象* @param beanName bean名称* @param definition 对象描述封装类* @return* @throws Exception*/public Object creatBean(String beanName, BeanDefinition definition) throws Exception {//创建当前对象==>且放入单例池中(单例的Map中)Class clazz = definition.getType();try {Object instance = clazz.getConstructor().newInstance();//判断当前的对象中是否有@autowide(依赖注入)注解 ,如果包含这个注解,需要将其字段进行赋值for (Field field : clazz.getDeclaredFields()) {if (field.isAnnotationPresent(Autowired.class)) {field.setAccessible(true);field.set(instance, getBean(field.getName()));}}//回到方法==>beanNameAwareif (instance instanceof BeanNameAware) {((BeanNameAware) instance).setBeanName(beanName);}//初始化前方法if (beanPostProcessorList.size() > 0) {for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);}}//初始化if (instance instanceof InitializingBean) {((InitializingBean) instance).afterPropertiesSet();}//初始化后(切面AOP)if (beanPostProcessorList.size() > 0) {for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);}}return instance;} catch (Exception e){throw new Exception("创建对象失败~~~",e);}}
}
3、创建对象后,需要提供需要获取Bean的方法
/*** 定于一个方法是获取bean资源的*/public Object getBean(String beanName) throws Exception {//判断当前的BeanDefinitionMap中是否存在beanName为key的beanDefinitionif (!beanDefinitionMap.containsKey(beanName)) {throw new Exception("当前beanName在BeanDefinitionMap中不存在~~~");}//从BeanDefinitionMap中获取到BeanDefinition信息==>判断其scopeBeanDefinition beanDefinition = beanDefinitionMap.get(beanName);//单例if ("singleton".equals(beanDefinition.getScope())) {Object singletonBean = singletonMap.get(beanName);if (singletonBean == null) {singletonBean = creatBean(beanName,beanDefinition);singletonMap.put(beanName,singletonBean);}return singletonBean;}else {//原型Object prototypeBean = creatBean(beanName,beanDefinition);return prototypeBean;}}
4、总结
总的来说,在Spring创建对象的过程中,主要分为,结合传入的类路径信息,扫描需要创建的对象资源=>结合上一步的扫描结果创建对象=>将创建好的对象提供一个对外获取Bean接口,具体详细过程图示:

相关文章:
【手写模拟Spring底层原理】
文章目录 模拟Spring底层详解1、结合配置类,扫描类资源1.1、创建需要扫描的配置类AppConfig,如下:1.2、创建Spring容器对象LyfApplicationContext,如下1.3、Spring容器对象LyfApplicationContext扫描资源 2、结合上一步的扫描&…...
代码随想录训练营Day1:二分查找与移除元素
本专栏内容为:代码随想录训练营学习专栏,用于记录训练营的学习经验分享与总结。 文档讲解:代码随想录 视频讲解:二分查找与移除元素 💓博主csdn个人主页:小小unicorn ⏩专栏分类:C 🚚…...
回归预测 | Matlab实现PCA-PLS主成分降维结合偏最小二乘回归预测
回归预测 | Matlab实现PCA-PLS主成分降维结合偏最小二乘回归预测 目录 回归预测 | Matlab实现PCA-PLS主成分降维结合偏最小二乘回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现PCA-PLS主成分降维结合偏小二乘回归预测(完整源码和数据) 1.输…...
高效的测试覆盖率:在更短的时间内最大化提高测试覆盖率
软件测试在敏捷开发生命周期中至关重要,而测试覆盖率又是软件测试的一个重要指标,有效的测试覆盖率对软件测试来说永远是重中之重。测试覆盖率确保所有关键功能和特性都经过彻底测试,减少最终产品中出现错误和错误的可能性(取决于…...
Qt 项目实战 | 音乐播放器
Qt 项目实战 | 音乐播放器 Qt 项目实战 | 音乐播放器播放器整体架构创建播放器主界面媒体对象状态实现播放列表实现桌面歌词添加系统托盘图标 资源下载 官方博客:https://www.yafeilinux.com/ Qt开源社区:https://www.qter.org/ 参考书:《Q…...
JavaScript使用Ajax
Ajax(Asynchronous JavaScript and XML)是使用JavaScript脚本,借助XMLHttpRequest插件,在客户端与服务器端之间实现异步通信的一种方法。2005年2月,Ajax第一次正式出现,从此以后Ajax成为JavaScript发起HTTP异步请求的代名词。2006…...
Python爬虫实战-批量爬取美女图片网下载图片
大家好,我是python222小锋老师。 近日锋哥又卷了一波Python实战课程-批量爬取美女图片网下载图片,主要是巩固下Python爬虫基础 视频版教程: Python爬虫实战-批量爬取美女图片网下载图片 视频教程_哔哩哔哩_bilibiliPython爬虫实战-批量爬取…...
uniapp+uview2.0+vuex实现自定义tabbar组件
效果图 1.在components文件夹中新建MyTabbar组件 2.组件代码 <template><view class"myTabbarBox" :style"{ backgroundColor: backgroundColor }"><u-tabbar :placeholder"true" zIndex"0" :value"MyTabbarS…...
opencv 任意两点切割图像
目录 opencv python直线切割图像,把图像分为两个多边形 升级版,把多边形分割抠图出来,取最小外接矩形:...
rust变量绑定、拷贝、转移、引用
目录 一,clone、copy 1,基本类型 2,类型的clone特征 3,显式声明结构体的clone特征 4,类型的copy特征 5,显式声明结构体的clone特征 5,变量和字面量的特征 6,特征总结 二&am…...
Java多种方式向图片添加自定义水印、图片转换及webp图片压缩
给个创建水印的示例: /*** 获取水印** param watermarkText 水印文字* return 水印bufferimage*/public static BufferedImage getWatermark(String watermarkText) {BufferedImage measureBufferdImage new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB…...
基于Pytorch框架的LSTM算法(二)——多维度单步预测
1.项目说明 **选用Close和Low两个特征,使用窗口time_steps窗口的2个特征,然后预测Close这一个特征数据未来一天的数据 当batch_firstTrue,则LSTM的inputs(batch_size,time_steps,input_size) batch_size len(data)-time_steps time_steps 滑动窗口&…...
cnn感受野计算方法
No. Layers Kernel Size Stride 1 Conv1 33 1 2 Pool1 22 2 3 Conv2 33 1 4 Pool2 22 2 5 Conv3 33 1 6 Conv4 33 1 7 Pool3 2*2 2 感受野初始值 l 0 1 l_0 1l 0 1,每层的感受野计算过程如下: l 0 1 l_0 1l 0 1 l 1 1 ( 3 − 1 ) 3 l_1 1…...
百分点科技受邀参加“第五届治理现代化论坛”
11月4日,由北京大学政府管理学院主办的“面向新时代的人才培养——第五届治理现代化论坛”举行,北京大学校党委常委、副校长、教务长王博,政府管理学院院长燕继荣参加开幕式并致辞,百分点科技董事长兼CEO苏萌受邀出席论坛…...
基于Springboot的智慧食堂设计与实现(有报告)。Javaee项目,springboot项目。
演示视频: 基于Springboot的智慧食堂设计与实现(有报告)。Javaee项目,springboot项目。 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 项…...
「Verilog学习笔记」多功能数据处理器
专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点,刷题网站用的是牛客网 分析 注意题目要求输入信号为有符号数,另外输出信号可能是输入信号的和,所以需要拓展一位,防止溢出。 timescale 1ns/1ns module data_…...
OpenHarmony 4.0 Release 编译异常处理
一、环境配置 编译环境:Ubuntu 20.04 OpenHarmony 软件版本:4.0 Release 设备平台:rk3568 二、下拉代码 参考官网步骤: OpenHarmony 4.0 Release 源码获取 repo init -u https://gitee.com/openharmony/manifest -b OpenHarmo…...
软件测试|MySQL LIKE:深入了解模糊查询
简介 在数据库查询中,模糊查询是一种强大的技术,可以用来搜索与指定模式匹配的数据。MySQL数据库提供了一个灵活而强大的LIKE操作符,使得模糊查询变得简单和高效。本文将详细介绍MySQL中的LIKE操作符以及它的用法,并通过示例演示…...
linux防火墙设置
#查看firewall的状态 firewall-cmd --state (systemctl status firewalld.service) #安装 yum install firewalld #启动, systemctl start firewalld (systemctl start firewalld.service) #设置开机启动 systemctl enable firewalld #关闭 systemctl stop firewalld #取消…...
http 403
一、什么是HTTP ERROR 403 403 Forbidden 是HTTP协议中的一个状态码(Status Code)。可以简单的理解为没有权限访问此站,服务器受到请求但拒绝提供服务。 二、HTTP 403 状态码解释大全 403.1 -执行访问禁止。 403.2 -读访问禁止。 403.3 -写访问禁止。 403.4要…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上,开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识,在 vs 2017 平台上,进行 ASP.NET 应用程序和简易网站的开发;初步熟悉开发一…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
淘宝扭蛋机小程序系统开发:打造互动性强的购物平台
淘宝扭蛋机小程序系统的开发,旨在打造一个互动性强的购物平台,让用户在购物的同时,能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机,实现旋转、抽拉等动作,增…...
