• Spring 的简单模拟实现


    目前进度

    Spring源码学习,进行简单模拟实现,一步步搭建。
    当前完成:

    1. ComponentScan 扫描指定路径包(不递归,且认为都是class文件,直接类加载方式)
    2. Scope 区分单例与多例Bean(单例Bean创建放入单例池)
    3. Autowired 依赖注入(不考虑循环依赖)
    4. BeanDefinition 只考虑了typescopelazy(未实现)
    5. InitializingBean IOC注入完成后执行afterPropertiesSet
    6. BeanPostProcessor 初始化前后执行前置后置方法
    7. Aware 普通对象依赖注入后,初始化前,注入Spring属性

    目的

    1. 通过手写模拟,了解Spring的底层源码启动过程
    2. 通过手写模拟,了解BeanDefinition、BeanPostProcessor的概念
    3. 通过手写模拟,了解Spring解析配置类等底层源码工作流程
    4. 通过手写模拟,了解依赖注入,Aware回调等底层源码工作流程
    5. 通过手写模拟,了解Spring AOP的底层源码工作流程

    搭建基础

    1. 创建一个 ApplicationContext 对象, 通过该对象读取一个 Config 配置类信息。
    2. 通过 ApplicationContext 对象的 getBean()方法获取 bean 对象, 并调用 bean 对象方法。
      基础框架文件目录

    KunApplicationContext

    package com.spring;
    
    /**
     * @author guokun
     * @date 2022/9/5 17:03
     */
    public class KunApplicationContext {
       
        private Class<?> configClass;
    
        public KunApplicationContext(Class<?> configClass) {
       
            this.configClass = configClass;
    
            // To Do: 根据Config中指定的扫描路径扫描
        }
    
        public Object getBean(String beanName) {
       
            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

    测试类 Test

    import com.kun.config.KunAppConfig;
    import com.kun.service.KunService;
    import com.spring.KunApplicationContext;
    
    /**
     * @author guokun
     * @date 2022/9/5 17:05
     */
    public class Test {
       
        public static void main(String[] args) {
       
            KunApplicationContext kunApplicationContext = new KunApplicationContext(KunAppConfig.class);
            KunService kunService = (KunService) kunApplicationContext.getBean("kunService");
            kunService.test();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    ComponentScan 注解

    package com.spring.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author guokun
     * @date 2022/9/5 17:17
     *
     * 运行时生效
     * 类注解
     *  扫描指定路径下得所有Component
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface ComponentScan {
       
        String value() default "";
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    Component 注解

    package com.spring.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author guokun
     * @date 2022/9/5 17:17
     * 运行时生效
     * 类注解
     *  声明为需要创建的bean类,可以指定bean的名字
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Component {
       
        String value() default "";
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    KunService 服务类(Bean类)

    package com.kun.service;
    
    import com.spring.annotation.Component;
    
    /**
     * @author guokun
     * @date 2022/9/5 17:15
     */
    @Component("kunService")
    public class KunService {
       
        public void test() {
       
            System.out.println("kunService test()");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    实现包扫描

    1. 包扫描流程在前面基础框架可以知道应该在 ApplicationContext 对象创建时进行。(构造方法中调用)
    public KunApplicationContext(Class<?> configClass) {
       
            this.configClass = configClass;
    
            componentScan();
        }
    
        private void componentScan(Class<?> configClass) {
       
               
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. 首先需要判断对应的 Config 类中有没有 ComponentScan注解,才可以扫描指定的包
    • 通过 isAnnotationPresent()查看某个类是否含有某个注解
    • 通过getAnnotation()获取某个类的某个注解对象
    private void componentScan() {
       
            // 扫描包
            if (configClass.isAnnotationPresent(ComponentScan.class)) {
       
                ComponentScan componentScanAnnotation = configClass.getAnnotation(ComponentScan.class);
                // 获取包路径 xx.xx.xx
                String packagePath = componentScanAnnotation.value();
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 由于JVM是读取class文件的,因此我们可以通过AppClassLoader来获取指定包的相对文件路径(注意需要将包格式转换为文件路径格式)
        private void componentScan() {
       
            // 扫描包
            if (configClass.isAnnotationPresent(ComponentScan.class)) {
       
                ComponentScan componentScanAnnotation = configClass.getAnnotation(ComponentScan.class);
                // 获取包路径 xx.xx.xx
                String packagePath = componentScanAnnotation.value();
                // 格式转换为 xx/xx/xx (因为我们要去加载的是class文件,通过文件系统去读取)
                String filePath = packagePath.replace(".", "/");
                // 获取类加载器(AppClassLoader)
                ClassLoader classLoader = KunApplicationContext.class.getClassLoader();
                // 使用AppClassLoader去获取指定包下的class文件夹路径
                URL targetClassUrl = classLoader.getResource(filePath);
    
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. 打开对应目录文件,并加载所有的Class,然后检查是否含有Component注解
    • 在Spring中使用ASM技术扫描,无需加载类,此处直接通过将所有类加载后判断是否有Component注解实现
    • 此处先只考虑当前包,不考虑子包
    • 假设该目录下所有文件都是class文件,不去考虑其他类型文件
    // 获取对应的文件目录
    assert targetClassUrl != null;
    File targetClassDir = new File(targetClassUrl.getFile());
    // 检查是否是一个文件目录
    if (targetClassDir.isDirectory()) {
       
        // 获取所有子文件 (先只考虑当前包,不考虑子包,假设子文件都是class文件)
        File[] classFiles = targetClassDir.listFiles();
        assert classFiles != null;
        List<Class<?>> classList = new ArrayList<>(classFiles.length);
        for (File classFile : classFiles) {
       
            // 获取class文件名称 xx.class
            String classFileName = classFile.getName();
            // 获取类名(去除.class)
            String className = classFileName.substring(0, classFileName.length() - 6);
            String classFullName = packagePath + "." + className;
            try {
       
                // 使用类加载器(AppClassLoader)加载类
                Class<?> loadClass = classLoader.loadClass(classFullName);
                // 检查是否含有Component注解
                if (loadClass.isAnnotationPresent(Component.class)) {
       
    
                }
            } catch (ClassNotFoundException e) {
       
                e.printStackTrace();
            }
        }
    }
    
    • 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
    1. 获取beanName,如果Component注解上为空串则使用类名的小驼峰命名作为
  • 相关阅读:
    单例模式只会懒汉饿汉?读完本篇让你面试疯狂加分
    Lua使用方式介绍
    vue-router实现history模式配置(生产环境路由失效跟刷新404问题)
    JS之函数
    基于ssm的互联网废品回收/基于web的废品资源利用系统
    猿创征文|【JavaSE】Java概述与配置问题解决
    wy的leetcode刷题记录_Day33
    Dynamics 365 重写自带按钮
    C语言 函数
    vue使用scope插槽实现dialog窗口
  • 原文地址:https://blog.csdn.net/Ctrl_kun/article/details/126709231