【SpringBoot底层原理】SpringBoot底层原理实践(一)——手撕SpringBoot容器(幼儿园版)

news2024/11/19 13:16:47

Spring底层原理实践(一)——手撕Spring容器(幼儿园版)

  • 0. 前言
  • 1. 依赖
  • 2. 注解
  • 3. Bean定义类
  • 4. 容器接口
  • 5. 配置类
  • 6. 测试Bean
  • 7. 启动类
  • 8. 容器实现类
    • 8.1 容器初始化
    • 8.2 获取Bean
    • 8.3 创建Bean
  • 9. 测试

0. 前言

SpringBoot项目创建一个helloworld的web项目非常快速且方便,然后内部的流程实际上非常复杂。很多像我一样的小白,想通过阅读源码方式来了解SpringBoot的运行流程和机制,会发现根本无从入手!!,想要先了解一个点,却发现一个点涉及的类和接口实在太多,难以梳理这个流程。
本文主要是对SpringBoot的其中一个核心功能进行探索:容器加载和获取Bean的基本方式。需要对SpringBoot稍微有一点基础理论,需要知道容器的概念,Bean的概念等。
全文主要内容有以下几个方面:

  1. 自定义注解@Component 和 @ComponentScan 来模拟SpringBoot的容器扫描的注解
  2. @Scope注解来标注一个Bean是否单例模式
  3. 通过一个接口ApplicationContext来模拟SpringBoot的ApplicationContext,以及它的实现类WhutApplicationContext完成Bean的加载和获取。
  4. 创建一个App类当做SpringBoot的启动类,
  5. 创建一个UserService类并添加上相应注解,来测试是否能成功注入到容器中。
    整个项目的目录结构如下:
    在这里插入图片描述

由于本文的很多类和注解跟SpringBoot框架自带的一样,所以在使用的时候注意一下,不要引用成框架的类了,如@Component,@ComponentScan、@Scope、BeanDefinition!!!!!!!!
`
本文内容主要参考自:Spring底层之bean底层原理,spring容器底层原理

1. 依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

2. 注解

@Component

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    String value() default ""; // 给当前Bean起一个名字
}

@ComponentScan

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
    String value() default  ""; // 扫描的路径
}

@Scope

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
    String value() default ""; // 设置单例 或者 原型模式
}

3. Bean定义类

package com.whut.spring.pojo;
 
/**
 * 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;
    }
}

4. 容器接口

ApplicationContext 接口

public interface ApplicationContext {
    Object getBean(String beanName);
    
    Object createBean(String beanName, BeanDefinition beanDefinition);

}

5. 配置类

@ComponentScan("com.whut.spring.service")
public class AppConfig {
}

6. 测试Bean

UserService

@Component("userService")
@Scope("prototype")
public class UserService {
}

StudentService

@Component
public class StudentService {
}

7. 启动类

在这个启动类中我们

/**
 * 模拟Spring的启动类
 *
 */
public class App 
{
    public static void main( String[] args )
    {
        ApplicationContext context = new WhutApplicationContext(AppConfig.class);

        UserService userService = (UserService) context.getBean("userService");
        System.out.println(userService);

        System.out.println();
        StudentService studentService = (StudentService) context.getBean("studentService");
        System.out.println(studentService);
    }
}

至此本文项目的框架已经搭建完毕,只剩下最后一件也是最为重要的一件事,就是ApplicationContext 的实现类。

8. 容器实现类

在实现类中,主要有以下3个方法。

public class WhutApplicationContext implements ApplicationContext{
    /*
        静态常量
     */
    private static final String SINGLETON = "singleton";

    /*
        存放Bean的Map
     */
    private static ConcurrentHashMap<String, Object> beanMap = new ConcurrentHashMap<>();
    /*
        存放Bean的定义信息的Map
     */
    private static ConcurrentHashMap<String, BeanDefinition> beanDefinitionHashMap = new ConcurrentHashMap<>();


    /**
     * 容器构造方法
     * @param configClass 配置类的Class
     */
    public WhutApplicationContext(Class<?> configClass) {
    
    }
 	 /**
     * 获取Bean
     * @param beanName
     * @return Bean的实例
     */
    @Override
    public Object getBean(String beanName) {
    
	}
	
    /**
     * 创建一个 bean (反转控制法)
     * @param beanName
     * @param beanDefinition
     * @return
     */
    @Override
    public Object createBean(String beanName,BeanDefinition beanDefinition){

	}

8.1 容器初始化

首先最为重要的就是容器的构造方法,在这个方法中,我们将完成Bean的扫描Bean的加载。通过传入一个带有@ComponentScan注解的类的class来确认扫描包的路径,从而完成Bean的扫描与加载。主要步骤如下:

        /*
        1. 传入的 configClass 类中有我们需要加入spring容器包的地址
        2. 通过这个地址,获取到该地址下的所有文件
        3. 遍历这些文件,并筛选出.class文件
        4. 通过这些.class文件的地址获取到他的类(控制反转)
        5. 判断类上面是否有@Component注解,并解析其名字,如名字为空,者以该类类名首字母小写设置为该类的名称。
        6. 创建一个bean的定义类(beanDefinition),用于包容放入容器的对象和它的一些配置(如:是否是单例,是否为懒加载),将对象和它的配置放入该类中。
        7. 创建一个总的bean容器,以对象名(或者Component注解中的value属性)作为key,存储beanDefinition。
         */

具体代码如下:

  /**
     * 容器构造方法
     * @param configClass 配置类的Class
     */
    public WhutApplicationContext(Class<?> configClass) {

        /*
        1. 传入的 configClass 类中有我们需要加入spring容器包的地址
        2. 通过这个地址,获取到该地址下的所有文件
        3. 遍历这些文件,并筛选出.class文件
        4. 通过这些.class文件的地址获取到他的类(控制反转)
        5. 判断类上面是否有@Component注解,并解析其名字,如名字为空,者以该类类名首字母小写设置为该类的名称。
        6. 创建一个bean的定义类(beanDefinition),用于包容放入容器的对象和它的一些配置(如:是否是单例,是否为懒加载),将对象和它的配置放入该类中。
        7. 创建一个总的bean容器,以对象名(或者Component注解中的value属性)作为key,存储beanDefinition。
         */

        if ((configClass).isAnnotationPresent(ComponentScan.class)) {
            // 1. 传入的 configClass 类中有我们需要加入spring容器包的地址
            ComponentScan componentScan =  configClass.getAnnotation(ComponentScan.class);
            String path = componentScan.value();
            path = path.replace(".", "/");  // 转为目录格式

            ClassLoader classLoader = WhutApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(path); // 获取绝对路径

            assert resource != null;
            File file = new File(resource.getFile());
            System.out.println("扫描到的资源路径为:" + file.getAbsolutePath());

            if (file.isDirectory()) {
                /*
                    2. 通过这个地址,获取到该地址下的所有文件
                 */
                File[] files = file.listFiles();
                assert files == null;
                for (File f : files) {
                    // 文件的绝对路径
                    String tempPath = f.getAbsolutePath();
                    /*
                        3. 遍历这些文件,并筛选出.class文件
                     */
                    if (tempPath.endsWith(".class")) {
                        // 获取 com/whut/spring/service/UserService.java
                        String className = tempPath.substring(tempPath.indexOf("com"), tempPath.indexOf(".class"));
                        // 转成 com.whut.spring.service.UserService
                        className = className.replace("\\",".");

                        /*
                            4. 判断类上面是否有@Component注解,并解析其名字,如名字为空,者以该类类名首字母小写设置为该类的名称。
                         */
                        try {
                            Class<?> aClass = classLoader.loadClass(className);
                            if (aClass.isAnnotationPresent(Component.class)) {
                                System.out.println("扫描到Component ==> " + className);
                                Component component = aClass.getAnnotation(Component.class);
                                /*
                                    5. 判断类上面是否有@Component注解,并解析其名字,如名字为空,者以该类类名首字母小写设置为该类的名称。
                                 */
                                String beanName = component.value();
                                if ("".equals(beanName)) {
                                    beanName = Introspector.decapitalize(aClass.getSimpleName());
                                }
                                /*
                                    6. 创建一个bean的定义类(beanDefinition),用于包容放入容器的对象和它的一些配置(如:是否是单例,是否为懒加载),将对象和它的配置放入该类中。
                                 */
                                BeanDefinition beanDefinition = new BeanDefinition();
                                beanDefinition.setType(aClass);

                                if (aClass.isAnnotationPresent(Scope.class)) {
                                    Scope scope = aClass.getAnnotation(Scope.class);
                                    beanDefinition.setScope(scope.value());
                                }else{
                                    beanDefinition.setScope(SINGLETON);
                                }

                                /*
                                    7. 创建一个总的bean容器,以对象名(或者Component注解中的value属性)作为key,存储beanDefinition。
                                 */
                                beanDefinitionHashMap.put(beanName, beanDefinition);
                            }
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }


            /*
                8. 实例化单例模式的Bean
             */

            //实例化单例bean
            for (String beanName : beanDefinitionHashMap.keySet()) {

                BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);

                if (beanDefinition.getScope().equals("singleton")) {// 判断是否为单例

                    Object bean = createBean(beanName,beanDefinition);
                    assert bean != null;
                    beanMap.put(beanName,bean);
                }
            }


        }
    }

8.2 获取Bean

在这个方法中,我们需要判断Bean是否为单例,如果是就直接返回(保证只有一个),如果不是,就新创建一个。

    /**
     * 获取Bean
     * @param beanName
     * @return Bean的实例
     */
    @Override
    public Object getBean(String beanName) {
        BeanDefinition beanDefinition = beanDefinitionHashMap.get(beanName);
        if (beanDefinition == null) {
            throw new NullPointerException();
        }else{
            String scope = beanDefinition.getScope();
            if (scope.equals(SINGLETON)) {//单例
                System.out.println("获取到一个 单例模式的Bean ==> " + beanName);
                Object bean = beanMap.get(beanName);
                if (bean == null){
                    Object tempBean = createBean(beanName, beanDefinition);
                    if (tempBean == null) throw new NullPointerException("创建Bean失败...");
                    beanMap.put(beanName, tempBean);
                }
                return bean;
            }else {//多例
                System.out.println("创建了一个非单例模式的Bean ==> " + beanName);
                return createBean(beanName,beanDefinition);
            }
        }
    }

8.3 创建Bean

/**
     * 创建一个 bean (反转控制法)
     * @param beanName
     * @param beanDefinition
     * @return
     */
    @Override
    public Object createBean(String beanName,BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getType();

        //通过类的构造器 生成一个类的对象
        try {
            return clazz.getConstructor().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

9. 测试

如果没有书写错误,支持,我们可以启动测试类了。启动后,控制台会打印如下信息:

扫描到Component ==> com.whut.spring.service.StudentService
扫描到Component ==> com.whut.spring.service.UserService
创建了一个非单例模式的Bean ==> userService
com.whut.spring.service.UserService@4ec6a292

获取到一个 单例模式的Bean ==> studentService
com.whut.spring.service.StudentService@1b40d5f0

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

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

相关文章

Character Animator 2024(Ch2024):打造生动角色,让动画设计更上一层楼

Character Animator 2024是一款专为角色动画设计师打造的软件&#xff0c;它可以帮助设计师快速创建出丰富多彩的角色动画。无论是初学者还是专业设计师&#xff0c;都可以通过Character Animator 2024轻松实现自己的创意。 Ch2024独特优势&#xff1a; 实时角色动画&#xf…

进程【Linux系统编程】

一、先谈硬件——冯诺依曼体系结构 存储器&#xff1a;内存&#xff08;硬盘是外存&#xff09; 输入设备&#xff1a;鼠标、键盘、摄像头、话筒、磁盘、网卡…… 输出设备&#xff1a;显示器、播放器硬件、磁盘、网卡…… 输入输出设备是外部设备&#xff0c;简称外设。 中央…

做一个最新版的淘宝客返利程序源码有多难?

我们都知道淘宝客返利程序成为了很多人的创业和赚钱的工具。这种程序允许通过推广淘宝商品来获得佣金。然而&#xff0c;你知道构建这样一个淘宝客返利程序有多难吗&#xff1f;今天我们就从最基本的API说起&#xff0c;现在我将介绍构建一个最新版淘宝客返利程序所需的关键API…

学习学不进去的时候怎么办?

01 人生很难只有欢声笑语,学习道路也一样。 有时候,我们会面临进退两难的情形: 明明知道应该学,却仿佛被定格在原地,一步也迈不出去。 这种状态为通常被称为“学不进去”&#xff0c;这时该怎么办? 其实, “学不进去”的原因可谓千头万绪,但归根结底,大抵可归为两类: 外在环境…

Python--练习:使用while循环求1..100的和

案例&#xff1a;使用while循环求1..100的和 思考&#xff1a; 先套用原有基础模式&#xff0c;之后再思考其他的。 while循环的基本语法&#xff1a; 对于循环次数已知的情况&#xff0c;建议使用while循环 因为是知道次数的&#xff0c;所以可以用一个计数器。程序里面&am…

【前端】图片裁剪路径绘制及图片不规则裁剪

说明 项目中可能需要用户根据展示的图片&#xff0c;然后绘制需要裁剪的路径&#xff0c;再根据绘制的坐标进行裁剪&#xff0c;以下是前端的裁剪路径绘制的代码示例&#xff0c;后端可以根据当前的获取到的坐标进行裁剪&#xff0c;裁剪的坐标保存在coordinate数组中。 代码 …

Hadoop3教程(二十八):(生产调优篇)NN、DN的多目录配置及磁盘间数据均衡

文章目录 &#xff08;148&#xff09;NN多目录配置&#xff08;149&#xff09;DataNode多目录配置及磁盘间数据平衡磁盘间数据均衡 参考文献 &#xff08;148&#xff09;NN多目录配置 NN多目录的意思是&#xff0c;本地目录可以配置成多个&#xff0c;且每个目录存放内容相…

用 Python 这样去创建词云不是更美嘛?

什么是词云&#xff1f;在网络上我们经常可以看到一张图片&#xff0c;上面有一大堆大小不一的文字&#xff0c;这便是词云。词云一般是根据输入的大量词语生成的&#xff0c;如果某个词语出现的次数越多&#xff0c;那么相应的大小就会越大。 Python 中有一个专门用来生成词云…

Spring的 @ControllerAdvice 之 ResponseBodyAdvice对响应结果进行增强

Spring的 ControllerAdvice 之 ResponseBodyAdvice对响应结果进行增强 1. 使用背景2. 使用方法3. 结果 1. 使用背景 对响应结果进行统一结果处理时&#xff0c;有时会出现有的接口未进行封装&#xff0c;为了解决该问题&#xff0c;可使用ControllerAdvice 注解对响应结果进行…

三辊闸机的应用领域和特点

三辊闸机是一种常用于门禁控制的设备&#xff0c;它具有以下应用和优点&#xff1a; 应用&#xff1a; 门禁控制&#xff1a;三辊闸机可以用于各种场合的门禁控制&#xff0c;如小区、写字楼、学校、医院等。考勤管理&#xff1a;三辊闸机可以与考勤系统集成&#xff0c;用于…

机器学习-K-近邻(KNN)算法

目录 一 . K-近邻算法&#xff08;KNN&#xff09;概述 二、KNN算法实现 三、 MATLAB实现 四、 实战 一 . K-近邻算法&#xff08;KNN&#xff09;概述 K-近邻算法&#xff08;KNN&#xff09;是一种基本的分类算法&#xff0c;它通过计算数据点之间的距离来进行分类。在…

算法通关村第二关-青铜终于学会链表了

大家好我是苏麟 , 今天来学反转链表 . 反转链表 描述 : 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 LeetCode 206.反转链表 : 206. 反转链表 牛客 BM1 反转链表 : 分析 : 本题有两种方法&#xff0c;带头结点和不带头结点&am…

【C语言精髓之指针】结构体指针(->与.两个运算符的区别)

/*** file * author jUicE_g2R(qq:3406291309)————彬(bin-必应)* 通信与信息专业大二在读 * copyright 2023.10* COPYRIGHT 原创技术笔记&#xff1a;转载需获得博主本人同意&#xff0c;且需标明转载源* language C/C* IDE Base on Mic…

下载安装Microsoft ODBC Driver for SQL Server和配置SQL Server ODBC数据源

1. 下载SQL Server ODBC驱动&#xff1a; Microsoft ODBC Driver for SQL Server - ODBC Driver for SQL Server | Microsoft Learn 2. 安装SQL Server ODBC驱动&#xff1a; 运行安装程序&#xff0c;出现如下图所示页面&#xff1b; 选择下一步&#xff1b;选择我同意许可协…

git学习——第2节 时光机穿梭

我们已经成功地添加并提交了一个readme.txt文件&#xff0c;现在&#xff0c;是时候继续工作了&#xff0c;于是&#xff0c;我们继续修改readme.txt文件&#xff0c;改成如下内容&#xff1a; Git is a distributed version control system. Git is free software. 现在&…

uni——底部弹框显示,底部导航隐藏

案例 在uni-app中&#xff0c;如果你在tabbar页面显示一个底部弹框&#xff0c;底部导航默认是会依旧显示的。如果你想在弹框显示时隐藏底部导航&#xff0c;你可以使用uni.hideTabBar和uni.showTabBar方法来控制底部导航的显示和隐藏。 export default {methods: {openPopup(…

汽车空调工作总结

工作总结 2022年3月加入公司&#xff0c;公司在河南&#xff0c;从事车载空调等相关项目&#xff0c;我的岗位是嵌入式软件工程师&#xff0c;在工作中也遇到了很多机遇和挑战&#xff0c;也学到了非常多的东西&#xff0c;在这里给大家分享下总结经验。 关于工作、公司 毕业…

线上答题活动小程序结合线下大屏复盘总结

线上答题活动小程序结合线下大屏复盘总结 ~ 说来话长&#xff0c;这个活动也接近尾声了&#xff0c;从刚开始着手开发&#xff0c;到现在已过去半年&#xff0c;好不夸张的&#xff0c;当时从4月份开始接触&#xff0c;现在已经十月份了 该小程序我发下主界面截图&#xff0…

ant提供对所有系统属性的访问

ant提供对所有系统属性的访问&#xff0c;就好像这些系统属性已经用 <property>任务定义过一样。 例如&#xff0c;下面的build文件中通过${os.name}获取操作系统名称&#xff0c;通过${java.home}获取Java的安装路径&#xff1a; <project name"demo_project&…

2023年最新版CorelDraw(cdr)软件下载安装教程

CorelDRAW 2023是Corel公司推出的最新版本的图形设计软件。CorelDRAW是一款功能强大的矢量图形编辑工具&#xff0c;被广泛用于图形设计、插图、页面布局、照片编辑和网页设计等领域。 1. 新增的设计工具&#xff1a;CorelDRAW 2023引入了一些全新的设计工具&#xff0c;使用户…