Spring 的简单模拟实现

本文通过逐步构建,模拟实现Spring的核心功能,包括包扫描、单例与多例Bean管理、依赖注入以及初始化前后的方法调用。文章详细介绍了如何处理BeanDefinition、BeanPostProcessor,并探讨了Spring的底层源码工作流程,帮助读者深入理解Spring框架。
摘要由CSDN通过智能技术生成

目前进度

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;
    }
}

测试类 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();
    }
}

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 "";
}

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 "";
}

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. 包扫描流程在前面基础框架可以知道应该在 ApplicationContext 对象创建时进行。(构造方法中调用)
public KunApplicationContext(Class<?> configClass) {
   
        this.configClass = configClass;

        componentScan();
    }

    private void componentScan(Class<?> configClass) {
   
           
    }
}
  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. 由于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. 打开对应目录文件,并加载所有的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. 获取beanName,如果Component注解上为空串则使用类名的小驼峰命名作为beanName
// 检查是否含有Component注解
if (loadClass.isAnnotationPresent(Component.class)) {
   
    // 获取beanName
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值