【手写模拟Spring底层原理】

news2025/1/16 10:59:43

文章目录

  • 模拟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;

    @Autowired
    private 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的值==>之定义的beanName
                            Component annotation = clazz.getAnnotation(Component.class);
                            String beanName = annotation.value();

                            //如果当前传入的beanName为空的话==>利用默认的,也就是类名首字母小写作为beanName
                            if ("".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的值==>之定义的beanName
                            Component annotation = clazz.getAnnotation(Component.class);
                            String beanName = annotation.value();

                            //如果当前传入的beanName为空的话==>利用默认的,也就是类名首字母小写作为beanName
                            if ("".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和value
           String 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()));
                }
            }
            //回到方法==>beanNameAware
            if (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的beanDefinition
        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new Exception("当前beanName在BeanDefinitionMap中不存在~~~");
        }
        //从BeanDefinitionMap中获取到BeanDefinition信息==>判断其scope
        BeanDefinition 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接口,具体详细过程图示:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1192012.html

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

相关文章

oracle11G在linux环境下的卸载操作

1.使用SQL*PLUS停止数据库 [oracleOracleTest oracle]$ sqlplus / as sysdba SQL> shutdown [immediate] SQL> exit2.停止Listener [oracleOracleTest oracle]$ lsnrctl stop3.停止HTTP服务 [rootOracleTest /root]# service httpd stop4.用su或者重新登录到root(如想…

bootstrap-fileinput拦截文件上传处理失败,根据后台返回数据处理

bootstrap-fileinput如何拦截后台数据&#xff0c;自定义处理业务逻辑 需要后台返回error字段&#xff0c;失败示例&#xff0c;注意&#xff1a;error必须有内容&#xff0c;不然默认也是成功&#xff0c; bootstrap-fileinput失败验证只需要 error 字段&#xff0c;其他附加…

【MySQL习题】各个视频的平均完播率【全网最详细教学】

目录 数据表描述 问题描述 输出示例 解题思路【重点】 正解代码 数据表描述 有以下两张表&#xff1a; 表1&#xff1a;用户-视频互动表tb_user_video_log 数据举例&#xff1a; 说明&#xff1a; uid-用户ID,video_id-视频ID start_time-开始观看时间end_time-结束观…

微信小程序报request:fail url not in domain list的解决方法

情况1&#xff1a;未设置合法域名 解决方法:请在微信公众平台登录小程序后台 > 开发管理 > 开发设置 > 服务器域名 情况2&#xff1a;设置了合法域名&#xff0c;开发工具仍然报错 解决方法: 在右上角点击详情&#xff0c;之后刷新一下项目配置&#xff0c;看看有…

一篇博客读懂单链表——Single-List

目录 一、初识单链表 单链表是如何构造的&#xff1a; 单链表如何解决顺序表中的问题&#xff1a; 二、单链表的初始定义 三、尾插和头插 3.1 新建结点CreateNode 3.2 打印SLTPrint 3.3 尾插SLTPushBack 3.4 头插SLTPushFront 四、尾删和头删 4.1 尾删SLTPopBack…

容器数据卷+MYSQL实战

什么是容器数据卷&#xff1f; 让我们回忆一下docker理念&#xff1a; 就是将应用和环境打包成一个镜像 数据&#xff1f; 如果数据都在容器中&#xff0c;那么我们删除容器&#xff0c;数据就会丢失 &#xff01;需求&#xff1a;数据持久化就完美了 对于MYSQL&#xff0…

玄子Share-Git 入门手册

玄子Share-Git 入门手册 简单介绍 Git Git 是一个自由和开源的分布式版本控制系统&#xff0c;旨在快速和高效地处理从小型到大型的所有项目 Git 简单易学&#xff0c;占用空间小&#xff0c;性能快如闪电。它比Subversion、CVS、Perforce和ClearCase等SCM工具更有优势&…

python默认的输入类型是字符串,怎样转换为其他的类型

在Python中&#xff0c;默认的输入类型是字符串&#xff08;str类型&#xff09;。无论你输入的是数字、字符还是其他类型的内容&#xff0c;input函数都会将其作为字符串处理并返回。 如果需要将字符串转换为其他类型&#xff08;如整数、浮点数等&#xff09;&#xff0c;可…

kafka和rocketMq的区别

kafka topic 中每一个分区会有 Leader 与 Follow。Kafka 的内部机制可以保证 topic 某一个分区的 Leader 与 Follow 不在同一台机器上 Leader 节点承担一个分区的读写&#xff0c;Follow 节点只负责数据备份 如果 Leader 分区所在的 Broker 节点宕机&#xff0c;会触发主从节…

Linux 的热插拔机制通过 Udev(用户空间设备)实现、守护进程

一、Udev作用概述 udev机制简介udev工作流程图 二、Linux的热拔插UDEV机制 三、守护进程 守护进程概念守护进程在后台运行基本特点 四、守护进程和后台进程的区别 一、Udev作用概述 udev机制简介 Udev&#xff08;用户空间设备&#xff09;是一个 Linux 系统中用于动态管…

玄子Share-HTML5知识手册

玄子Share-HTML5知识手册 前言&#xff1a; 这一版 HTML 笔记&#xff0c;算是我写的第四版了&#xff0c;第三版对照课本编写&#xff0c;第四版则是对照 MDN 官方文档编写&#xff0c;不论是术语亦或专业性&#xff0c;都更上一层 文章依托 MDN 文档&#xff0c;拓展了大量课…

【2023-11-09】git使用随记——gitignore文件配置某些文件忽略

git使用随记——gitignore文件配置某些文件忽略 通过git进行版本控制在项目中是非常常见的&#xff0c;一些项目构建上的文件通常是不需要进行版本控制的&#xff0c;也就无需推送到git仓库中&#xff0c;比如前端项目中的node_module目录。提供配置.gitignore文件 但是某些情…

腾讯云88,阿里云99,现在都这么卷了吗?!

你是否曾经想过&#xff0c;云服务器的价格竟然可以如此亲民&#xff1f;现在&#xff0c;腾讯云和阿里云竟然都推出了超低价位的云服务器&#xff0c;只要88元和99元&#xff01;这让我们这些自媒体人、创业者、开发者等都感到非常惊喜。可以看一下配置和价格&#xff1a; 可…

MATLAB中Line 属性说明

目录 颜色和样式 位置 Line 属性是注释线条的外观和行为。 Line 属性控制 Line 对象的外观和行为。通过更改属性值&#xff0c;可以修改线条的特定方面。使用圆点表示法查询和设置属性。 h annotation("line"); c h.Color; h.Color "red"; 颜色和样…

边缘计算如何改变数据存储?

边缘计算在整个价值链中提供多种优势——从降低成本到提高效率再到安全数据传输。该技术允许在源头收集和分析相关数据&#xff0c;这有助于减少延迟和带宽成本&#xff0c;同时显著提高计算过程的冗余系数和效率。 通过降低数据传输成本和损失&#xff0c;边缘计算帮助企业实现…

docker更改存储目录原因及方案

为什么一定要将docker的存储目录挂载到其他目录 docker在安装时默认存储目录在/var/lib/docker&#xff0c;而该目录是在系统盘下的。docker安装后&#xff0c;会使用各种各样的镜像&#xff0c;动辄几个G&#xff0c;那么如此多的镜像文件&#xff0c;装着装着系统盘就撑爆了…

【AI】自回归 (AR) 模型使预测和深度学习变得简单

自回归 (AR) 模型是统计和时间序列模型&#xff0c;用于根据数据点的先前值进行分析和预测。这些模型广泛应用于各个领域&#xff0c;包括经济、金融、信号处理和自然语言处理。 自回归模型假设给定时间变量的值与其过去的值线性相关&#xff0c;这使得它们可用于建模和预测时…

【Unity细节】Unity中的Transform.SetParent还有你不知道的细节

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 &#x1f636;‍&#x1f32b;️收录于专栏&#xff1a;unity细节和bug &#x1f636;‍&#x1f32b;️优质专栏 ⭐【…

除了鲁大师,你还可以装这些。

无论是专业的电脑维护人员&#xff0c;还是只是一个简单的 DIY 兴趣爱好者&#xff0c;有的时候都会或多或少遇到一些电脑硬件的问题。 这种情况下&#xff0c;如果特地去安装一款专门的检测软件&#xff0c;肯定会非常麻烦。 一般来说&#xff0c;检测电脑硬件&#xff0c;使…

【解刊】可投!Elsevier旗下1/2区SCI,4天见刊!

计算机类 • 好刊解读 今天小编带来Elsevier旗下计算机领域好刊的解读&#xff0c;如有相关领域作者有意向投稿&#xff0c;可作为重点关注&#xff01;后文有同领域快刊发表案例&#xff0c;供您投稿参考~ 01 期刊简介 Sustainable Computing: Informatics and Systems ☑️…