当前位置: 首页 > news >正文

手写模拟spring扫描底层实现

手写模拟spring扫描底层实现

  • 前言
    • 1、ApplicationContext和AppConfig
    • 2、@Component和@ComponentScan注解
    • 3、UserService和Test


前言

最近也是开始学习了Spring的一些底层,学习的最好方法就是自己实现,所以也是跟着视频自己手写一个模拟spring,其中对一些知识也是会总结和讲解自己的理解。

1、ApplicationContext和AppConfig

我们首先需要模拟的是spring的自动扫描机制,我们来想一下,当spring容器启动,自动扫描的时候,首先是肯定不能缺少启动类的,也就是我们的ApplicationContext,所以我们先创建一个ApplicationContext。

回想一下,我们之前手动去配置spring.xml的时候,是要通过启动配置ApplicationContext将配置文件进行读取,所以我们先要写一个spring的配置类 AppConfig,当然,我们这里并不进行一些复杂的配置。

/**
 * @author zal
 * @date 2022年08月30日 16:23
 * @Description Spring容器配置类
 */
@ComponentScan("com.zal.service")
public class AppConfig {
}

然后将这个AppConfig注入到我们的启动类ZalApplicationContext中,实现基本的配置读取。

/**
 * @author zal
 * @date 2022年08月30日 16:21
 * @Description 模拟Spring容器启动类
 */
public class ZalApplicationContext {

    private Class configClass;

	/**
     * 创建一个Spring容器
     *
     * @param configClass
     */
    public ZalApplicationContext(Class configClass) {
        this.configClass = configClass;
    }
}

2、@Component和@ComponentScan注解

我们知道,Spring的扫描是通过类上是否加上了注解@Component来进行判断的,而@ComponentScan 注解则是配置扫描包的路径的,所以我们要实现一下这两个注解。

/**
 * @author zal
 * @date 2022年08月30日 16:20
 * @Description 模仿spring的bean注入的注解
 * @Target(ElementType.TYPE)  表示只能作用在类的上面
 * @Retention(RetentionPolicy.RUNTIME) 表示作用在运行时
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    String value() default "";
}
/**
 * @author zal
 * @date 2022年08月30日 16:20
 * @Description 模仿spring路径扫描的注解
 * @Target(ElementType.TYPE)  表示只能作用在类的上面
 * @Retention(RetentionPolicy.RUNTIME) 表示作用在运行时
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {
    String value() default "";
}

3、UserService和Test

拥有了Spring启动类以及相关扫描的注解之后,我们需要一个被扫描的服务以及对应的测试方法,所以我们这里自定义一个UserService,给这个类加上相应的注解后进行扫描逻逻辑的编写。

@Component("userService")
public class UserService {
	// 代码
}
public class Test {
    public static void main(String[] args) {
        ZalApplicationContext applicationContext = new ZalApplicationContext(AppConfig.class);
        // 我们拿到spring容器后,肯定是要进行获取bean的操作的
        // 获取bean的操作我们放在下篇文章讲解
        applicationContext.getBean("userService");
    }
}

最后是ZalApplicationContext进行扫描的逻辑实现,如下代码

public class ZalApplicationContext {

    private Class configClass;
    /**
     * 创建一个Spring容器
     *
     * @param configClass
     */
    public ZalApplicationContext(Class configClass) {
        this.configClass = configClass;
        // 扫描
        // 判断传入的配置类上是否添加了ComponentScan注解
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            // 获取注解的信息
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            // 获取扫描路径(就是注解上添加的包路径),如com.zal.service
            String path = componentScanAnnotation.value();
            // 处理路径字符串,将.替换为/,如com/zal/service
            path = path.replace(".", "/");

            //获取容器的类加载器,获取class文件的绝对路径 如F:/web/zal-spring/out/production/zal-spring/com/zal/service
            ClassLoader classLoader = ZalApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(path);
            // 将class文件封装为一个文件或者是文件夹
            File file = new File(resource.getFile());

            if (file.isDirectory()) {
                // 拿到当前文件夹中的所有文件
                File[] files = file.listFiles();
                for (File f : files) {
                    // 拿到文件的绝对路径
                    String fileName = f.getAbsolutePath();
                    // 判断文件是否以class结尾
                    if (fileName.endsWith(".class")) {
                        // 获取class文件的全限定类名,如com.zal.service.UserService
                        String className = fileName
                                .substring(fileName.indexOf("com"), fileName.indexOf(".class"))
                                .replace("\\", ".");
                        try {
                            // 如何获取class对象呢? 这就涉及到类的加载
                            Class<?> clazz = classLoader.loadClass(className);
                            // 判断这个类是否是一个bean,条件是类上是否有@Component注解
                            if (clazz.isAnnotationPresent(Component.class)) {
                              	//bean的创建操作
                            }
                        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

以上实现只是一个简单的模拟spring底层扫描的逻辑代码,表示spring扫描的一个简单的逻辑,并不是真正的spring底层扫描实现,只是为了更好的让我们去了解底层实现的一个思想。

相关文章:

  • 照片拼图软件哪个好?快来看看这几个软件
  • 力扣打卡之合并两个有序数组
  • 通过划分法优化共识算法-“Scaling Replicated State Machines with Compartmentalization”详解
  • C#进阶04——委托和事件
  • MySQL数据库 增删查改案例讲解
  • 【面试入门必刷】算法入门-数据结构-栈(一)
  • 《论文复现》MOJITALK: Generating Emotional Responses at Scale 部分过程讲解
  • GBase 8s 安全性(6)- 备份与恢复
  • 【人工智能】神经网络八股扩展
  • 如何获取大数据行业高薪岗位offer?
  • mac mongodb6.0.1安装
  • Spring常见问题解决 - @EnableWebMvc 导致自定义序列化器失效
  • 深入理解JVM(一)JVM与Java体系结构
  • 【数据分享】 中国五批3610个国家级非物质文化遗产空间分布数据
  • 7、JAVA入门——if选择结构
  • [rust! #004] [译] Rust 的内置 Traits, 使用场景, 方式, 和原因
  • 【JavaScript】通过闭包创建具有私有属性的实例对象
  • 5分钟即可掌握的前端高效利器:JavaScript 策略模式
  • ES6 ...操作符
  • ES6系列(二)变量的解构赋值
  • Git 使用集
  • js面向对象
  • Js实现点击查看全文(类似今日头条、知乎日报效果)
  • magento 货币换算
  • Next.js之基础概念(二)
  • Spring Cloud Feign的两种使用姿势
  • webgl (原生)基础入门指南【一】
  • 百度贴吧爬虫node+vue baidu_tieba_crawler
  • 创建一个Struts2项目maven 方式
  • 从0到1:PostCSS 插件开发最佳实践
  • 关于Flux,Vuex,Redux的思考
  • 入职第二天:使用koa搭建node server是种怎样的体验
  • 试着探索高并发下的系统架构面貌
  • 腾讯视频格式如何转换成mp4 将下载的qlv文件转换成mp4的方法
  • 微信支付JSAPI,实测!终极方案
  • 正则表达式
  • 智能合约Solidity教程-事件和日志(一)
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • 说说我为什么看好Spring Cloud Alibaba
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • ![CDATA[ ]] 是什么东东
  • ## 临床数据 两两比较 加显著性boxplot加显著性
  • ###项目技术发展史
  • #每天一道面试题# 什么是MySQL的回表查询
  • (4)事件处理——(7)简单事件(Simple events)
  • (备忘)Java Map 遍历
  • (多级缓存)多级缓存
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (力扣)1314.矩阵区域和
  • (原創) 如何安裝Linux版本的Quartus II? (SOC) (Quartus II) (Linux) (RedHat) (VirtualBox)
  • (原創) 如何讓IE7按第二次Ctrl + Tab時,回到原來的索引標籤? (Web) (IE) (OS) (Windows)...
  • (转)程序员疫苗:代码注入
  • ./mysql.server: 没有那个文件或目录_Linux下安装MySQL出现“ls: /var/lib/mysql/*.pid: 没有那个文件或目录”...
  • .NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划
  • .NET/C# 编译期能确定的字符串会在字符串暂存池中不会被 GC 垃圾回收掉