【手写模拟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;

    @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接口,具体详细过程图示:
在这里插入图片描述
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue3 文字轮播打字机效果

实现效果 1.安装依赖 npm install duskmoon/vue3-typed-js 2.html <div class"title_left_1"><Typed :options"options" class"typedClass"><div class"typing"></div></Typed> </div> 3.ts…

Vue 组件化编程 和 生命周期

目录 一、组件化编程 1.基本介绍 : 2.原理示意图 : 3.全局组件示例 : 4.局部组件示例 : 5.全局组件和局部组件的区别 : 二、生命周期 1.基本介绍 : 2.生命周期示意图 : 3.实例测试 : 一、组件化编程 1.基本介绍 : (1) 开发大型应用的时候&#xff0c;页面往往划分成…

行情分析——加密货币市场大盘走势(11.10)

大饼今日继续上涨&#xff0c;正如预期&#xff0c;跌不下来&#xff0c;思路就是逢低做多。现在已经上涨到36500附近&#xff0c;目前从MACD日线来看&#xff0c;后续还要继续上涨&#xff0c;当然稳健的可以不做。昨日的策略已经达到止盈&#xff0c;也是顺利的落袋为安啦。一…

局域网下搭建SVN服务器

文章目录 1. 下载SVN服务器(VisualSVN Server)2. 安装SVN服务器(VisualSVN Server)3. 下载并安装TortoiseSVN4. 搭建SVN服务器 1. 下载SVN服务器(VisualSVN Server) 下载地址 2. 安装SVN服务器(VisualSVN Server) 默认安装即可 Location&#xff1a;VisualSVN Server的安装…

【chat】2:vs2022 连接远程ubuntu服务器远程cmake开发

大神们是使用vs远程连接和调试的:C++搭建集群聊天室(三):配置远程代码编辑神器 VScode我尝试过vs++ 和 clion 都不错。在 Visual Studio 中配置 Linux CMake 项目 比较麻烦的就是要配置CMakeSettings.json ,而且会自动做复制指定远程 Linux 目标,则会将源复制到远程系统 …

《单链表》的实现(不含哨兵位的单向链表)

目录 ​编辑 前言&#xff1a; 链表的概念及结构&#xff1a; 链表的实现&#xff1a; 1.typedef数据类型&#xff1a; 2.打印链表 &#xff1a; 3.创建新节点&#xff1a; 4.尾插 &#xff1a; 5.头插&#xff1a; 6.尾删 &#xff1a; 7.头删&#xff1a; 8.查找节…

刚安装的MySQL使用Navicat操作数据库遇到的问题

刚安装的MySQL使用Navicat操作数据库遇到的问题 一、编辑连接保存报错二、打开数据表很慢三、MySQL的进程出现大量“sleep”状态的进程四、执行sql脚本报错&#xff0c;部分表导不进去五、当前MySQL配置文件 一、编辑连接保存报错 连接上了数据库&#xff0c;编辑连接保存报错…

winform打包默认安装路径设置

点击安装程序的 Application Folder 修改属性中的 DefaultLocation

竞赛选题 深度学习疲劳驾驶检测 opencv python

文章目录 0 前言1 课题背景2 实现目标3 当前市面上疲劳驾驶检测的方法4 相关数据集5 基于头部姿态的驾驶疲劳检测5.1 如何确定疲劳状态5.2 算法步骤5.3 打瞌睡判断 6 基于CNN与SVM的疲劳检测方法6.1 网络结构6.2 疲劳图像分类训练6.3 训练结果 7 最后 0 前言 &#x1f525; 优…

云效流水线docker部署 :node.js镜像部署VUE项目

文章目录 引言I 流水线配置1.1 项目dockerfile1.2 Node.js 镜像构建1.3 docker 部署引言 云效流水线配置实现docker 部署微服务项目:https://blog.csdn.net/z929118967/article/details/133687120?spm=1001.2014.3001.5501 配置dockerfile-> 镜像构建->docker部署。 …

linux中使用arthas进行jvm内存分析

1. 安装下载 首先在官方github地址选择合适的版本&#xff0c;下载后上传到对于服务器。 使用unzip arthas-bin.zip 解压文件。进入目录中&#xff0c;执行./install-local.sh进行安装。执行完成后提示succeed&#xff0c;即可使用。 2. 启动 进入目录&#xff0c;执行java…

【优选算法系列】【专题七分治】第一节.75. 颜色分类和912. 排序数组

文章目录 前言一、颜色分类 1.1 题目描述 1.2 题目解析 1.2.1 算法原理 1.2.2 代码编写二、排序数组 2.1 题目描述 2.2 题目解析 2.2.1 算法原理 2.2.2 代码编写总结 前言 一、颜色分类 1.1 题目描述 描述&…

续:将基于Nasm汇编的打字小游戏,移植到DOSBox

续&#xff1a;将基于Nasm汇编的打字小游戏&#xff0c;移植到DOSBox 文章目录 续&#xff1a;将基于Nasm汇编的打字小游戏&#xff0c;移植到DOSBox前情提要细说1 编译2 程序入口3 定位段 运行体验 前情提要 上一篇&#xff1a;【编程实践】黑框框里的打字小游戏&#xff0c;但…

我的月光宝盒初体验失败了

哈哈哈&#xff0c;我爱docker, docker 使我自由&#xff01;&#xff01;&#xff01; docker make me free! 菠萝菠萝蜜口号喊起来。 https://github.com/vivo/MoonBox/ windows上安装好了docker之后&#xff0c;docker-compose是自带的。 docker-compose -f docker-compo…

SpringBoot核心知识点总结【Spring Boot 复习】

文章目录 Spring Boot 精要1. 自动配置2. 起步依赖3. 命令行界面4. Actuator 开发SpringBoot程序1. 启动引导Spring2. 测试Spring Boot应用程序3. 配置应用程序属性2.2 使用起步依赖2.3 使用自动配置专注于应用程序功能 Spring Boot 精要 Spring Boot将很多魔法带入了Spring应…

KiB、MiB与KB、MB的区别

KiB、MiB与KB、MB的区别

智安网络|数据库入门秘籍:通俗易懂,轻松掌握与实践

在现代信息化时代&#xff0c;数据库已成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;对于非专业人士来说&#xff0c;数据库这个概念可能很抽象&#xff0c;难以理解。 一、什么是数据库&#xff1f; 简单来说&#xff0c;数据库是一个存储和管理数据的系统。它…

城市内涝积水监测,万宾科技内涝预警监测系统

每一个城市的排水体系都是一个复杂的网络系统&#xff0c;需要多个部分配合协调&#xff0c;预防城市排水管网带来安全隐患&#xff0c;也因此才能在一定程度上缓解城市内涝带来的安全问题。在海绵城市建设过程中不仅要解决大部分道路硬化导致的积水无法渗透等问题&#xff0c;…

抢抓泛娱乐社交出海新风口!Flat Ads深圳沙龙活动引爆海外市场

随着全球化进程的加速&#xff0c;中国的应用类APP不断走向国际市场。作为产品和服务的提供者&#xff0c;中国开发者围绕社交泛娱乐创新&#xff0c;开启直播出海、短视频出海、游戏社交出海、1V1 视频出海、音频社交出海等出海热潮。“社交、泛娱乐”融合成为行业主流发展趋势…

Redis的内存淘汰策略分析

概念 LRU 是按访问时间排序&#xff0c;发生淘汰的时候&#xff0c;把访问时间最久的淘汰掉。LFU 是按频次排序&#xff0c;一个数据被访问过&#xff0c;把它的频次 1&#xff0c;发生淘汰的时候&#xff0c;把频次低的淘汰掉。 几种LRU策略 以下集中LRU测率网上有很多&am…