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

【手写模拟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、结合配置类&#xff0c;扫描类资源1.1、创建需要扫描的配置类AppConfig&#xff0c;如下&#xff1a;1.2、创建Spring容器对象LyfApplicationContext&#xff0c;如下1.3、Spring容器对象LyfApplicationContext扫描资源 2、结合上一步的扫描&…...

代码随想录训练营Day1:二分查找与移除元素

本专栏内容为&#xff1a;代码随想录训练营学习专栏&#xff0c;用于记录训练营的学习经验分享与总结。 文档讲解&#xff1a;代码随想录 视频讲解&#xff1a;二分查找与移除元素 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;C &#x1f69a…...

回归预测 | Matlab实现PCA-PLS主成分降维结合偏最小二乘回归预测

回归预测 | Matlab实现PCA-PLS主成分降维结合偏最小二乘回归预测 目录 回归预测 | Matlab实现PCA-PLS主成分降维结合偏最小二乘回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现PCA-PLS主成分降维结合偏小二乘回归预测&#xff08;完整源码和数据) 1.输…...

高效的测试覆盖率:在更短的时间内最大化提高测试覆盖率

软件测试在敏捷开发生命周期中至关重要&#xff0c;而测试覆盖率又是软件测试的一个重要指标&#xff0c;有效的测试覆盖率对软件测试来说永远是重中之重。测试覆盖率确保所有关键功能和特性都经过彻底测试&#xff0c;减少最终产品中出现错误和错误的可能性&#xff08;取决于…...

Qt 项目实战 | 音乐播放器

Qt 项目实战 | 音乐播放器 Qt 项目实战 | 音乐播放器播放器整体架构创建播放器主界面媒体对象状态实现播放列表实现桌面歌词添加系统托盘图标 资源下载 官方博客&#xff1a;https://www.yafeilinux.com/ Qt开源社区&#xff1a;https://www.qter.org/ 参考书&#xff1a;《Q…...

JavaScript使用Ajax

Ajax(Asynchronous JavaScript and XML)是使用JavaScript脚本&#xff0c;借助XMLHttpRequest插件&#xff0c;在客户端与服务器端之间实现异步通信的一种方法。2005年2月&#xff0c;Ajax第一次正式出现&#xff0c;从此以后Ajax成为JavaScript发起HTTP异步请求的代名词。2006…...

Python爬虫实战-批量爬取美女图片网下载图片

大家好&#xff0c;我是python222小锋老师。 近日锋哥又卷了一波Python实战课程-批量爬取美女图片网下载图片&#xff0c;主要是巩固下Python爬虫基础 视频版教程&#xff1a; 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变量绑定、拷贝、转移、引用

目录 一&#xff0c;clone、copy 1&#xff0c;基本类型 2&#xff0c;类型的clone特征 3&#xff0c;显式声明结构体的clone特征 4&#xff0c;类型的copy特征 5&#xff0c;显式声明结构体的clone特征 5&#xff0c;变量和字面量的特征 6&#xff0c;特征总结 二&am…...

Java多种方式向图片添加自定义水印、图片转换及webp图片压缩

给个创建水印的示例&#xff1a; /*** 获取水印** 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两个特征&#xff0c;使用窗口time_steps窗口的2个特征&#xff0c;然后预测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&#xff0c;每层的感受野计算过程如下&#xff1a; l 0 1 l_0 1l 0 ​ 1 l 1 1 ( 3 − 1 ) 3 l_1 1…...

百分点科技受邀参加“第五届治理现代化论坛”

11月4日&#xff0c;由北京大学政府管理学院主办的“面向新时代的人才培养——第五届治理现代化论坛”举行&#xff0c;北京大学校党委常委、副校长、教务长王博&#xff0c;政府管理学院院长燕继荣参加开幕式并致辞&#xff0c;百分点科技董事长兼CEO苏萌受邀出席论坛&#xf…...

基于Springboot的智慧食堂设计与实现(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的智慧食堂设计与实现&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 项…...

「Verilog学习笔记」多功能数据处理器

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 分析 注意题目要求输入信号为有符号数&#xff0c;另外输出信号可能是输入信号的和&#xff0c;所以需要拓展一位&#xff0c;防止溢出。 timescale 1ns/1ns module data_…...

OpenHarmony 4.0 Release 编译异常处理

一、环境配置 编译环境&#xff1a;Ubuntu 20.04 OpenHarmony 软件版本&#xff1a;4.0 Release 设备平台&#xff1a;rk3568 二、下拉代码 参考官网步骤&#xff1a; OpenHarmony 4.0 Release 源码获取 repo init -u https://gitee.com/openharmony/manifest -b OpenHarmo…...

软件测试|MySQL LIKE:深入了解模糊查询

简介 在数据库查询中&#xff0c;模糊查询是一种强大的技术&#xff0c;可以用来搜索与指定模式匹配的数据。MySQL数据库提供了一个灵活而强大的LIKE操作符&#xff0c;使得模糊查询变得简单和高效。本文将详细介绍MySQL中的LIKE操作符以及它的用法&#xff0c;并通过示例演示…...

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)。可以简单的理解为没有权限访问此站&#xff0c;服务器受到请求但拒绝提供服务。 二、HTTP 403 状态码解释大全 403.1 -执行访问禁止。 403.2 -读访问禁止。 403.3 -写访问禁止。 403.4要…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

基于matlab策略迭代和值迭代法的动态规划

经典的基于策略迭代和值迭代法的动态规划matlab代码&#xff0c;实现机器人的最优运输 Dynamic-Programming-master/Environment.pdf , 104724 Dynamic-Programming-master/README.md , 506 Dynamic-Programming-master/generalizedPolicyIteration.m , 1970 Dynamic-Programm…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)

本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill

视觉语言模型&#xff08;Vision-Language Models, VLMs&#xff09;&#xff0c;为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展&#xff0c;机器人仍难以胜任复杂的长时程任务&#xff08;如家具装配&#xff09;&#xff0c;主要受限于人…...

PHP 8.5 即将发布:管道操作符、强力调试

前不久&#xff0c;PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5&#xff01;作为 PHP 语言的又一次重要迭代&#xff0c;PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是&#xff0c;借助强大的本地开发环境 ServBay&am…...

在 Spring Boot 项目里,MYSQL中json类型字段使用

前言&#xff1a; 因为程序特殊需求导致&#xff0c;需要mysql数据库存储json类型数据&#xff0c;因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...