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


    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.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2. 注解

    @Component

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

    @ComponentScan

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface ComponentScan {
        String value() default  ""; // 扫描的路径
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    @Scope

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

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    4. 容器接口

    ApplicationContext 接口

    public interface ApplicationContext {
        Object getBean(String beanName);
        
        Object createBean(String beanName, BeanDefinition beanDefinition);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5. 配置类

    @ComponentScan("com.whut.spring.service")
    public class AppConfig {
    }
    
    
    • 1
    • 2
    • 3
    • 4

    6. 测试Bean

    UserService

    @Component("userService")
    @Scope("prototype")
    public class UserService {
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    StudentService

    @Component
    public class StudentService {
    }
    
    • 1
    • 2
    • 3

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    至此本文项目的框架已经搭建完毕,只剩下最后一件也是最为重要的一件事,就是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){
    
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    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。
             */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    具体代码如下:

      /**
         * 容器构造方法
         * @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);
                    }
                }
    
    
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108

    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);
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    linux(ARM)架构下QT连接mysql
    InnoDB数据存储结构
    docker使用记录1:构建java、python、c++环境镜像
    时序预测 | Pytorch实现TCN-Transformer的时间序列预测
    【深度学习】第四章:循环神经网络
    js写轮播图,逐步完善
    音视频实战---音视频解码
    01-Kafaka
    【Leetcode Sheet】Weekly Practice 5
    【21天算法挑战赛】排序算法——冒泡排序
  • 原文地址:https://blog.csdn.net/God_WZH/article/details/133923453