手写框架(一)之spring(上)

本文介绍了如何从零开始手写Spring框架,包括创建简单ApplicationContext、自定义@ComponentScan注解、扫描包路径及bean的单例和原型概念。通过实践理解Spring框架核心原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

大家好,今天我想和大家分享一个令人激动的话题——手写框架!随着技术的不断发展,我们对各种流行框架的使用已经司空见惯。但是,你是否想过框架背后的原理和工作原理?我相信很多人都曾经有过一种冲动,想要亲手实现一个属于自己的框架。

在这个系列的博客中,我将带领大家一起探索手写框架的奇妙之处。我们将以Spring框架为例,从最基础的部分开始,逐步揭开其内部实现的神秘面纱。通过手写框架的过程,我们可以深入了解框架背后的设计思想和核心原理,提升自己的技术水平。

本篇博客将聚焦于手写框架系列的第一部分,即手写一个简单的Spring框架。我们将从零开始,逐步搭建起一个简单但完整的Spring框架的原型。通过这个过程,我们将深入理解Spring的核心概念和基本功能,并通过自己的实践加深对框架的理解。

让我们一起踏上这个有趣而挑战性的手写框架之旅吧!在接下来的博客中,我将分享实现Spring框架所需的基本步骤、关键技术和遇到的问题。希望能够为大家提供一些有价值的经验和启发。

Spring的启动和扫描逻辑实现

spring的启动

首先,我们使用idea创建一个简单的Java项目,并在com包下创建两个包,一个是我们手写的spring,另一个就是测试包

我们在配置spring时,会在main方法中创建一个context ,并将Config的类传进去

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(YourConfigClass.class);

那么我们就来创建一个自己的ApplicationContext

public class XiaoNanApplicationContext {
    private Class configClass;//配置类
    //通过构造方法,传入配置类
    public XiaoNanApplicationContext(Class configClass) {
        this.configClass = configClass;
    }
}

我们这个配置类中,需要有什么内容呢?最基本的就是需要有bean的扫描包路径

也就是在注解@ComponentScan("")中填写包的扫描路径,那么我们现在就要将这个注解创建出来

package com.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

这两个是Java中的注解定义,用于指定注解的保留策略和作用目标。
@Retention(RetentionPolicy.RUNTIME) 指定了注解的保留策略为运行时(Runtime),表示该注解在程序运行期间仍然可用。这意味着在运行时可以通过反射获取到被注解的信息。 @Target(ElementType.TYPE) 指定了注解的作用目标为类或接口(TYPE),表示该注解可以应用于类或接口的声明上。 

 那么我们现在有了@ComponentScan("")注解,就可以来编写配置类

package com.xiaonan;

import com.spring.ComponentScan;

//定义包的扫描路径
@ComponentScan("com.xiaonan.service")
public class AppConfig {
}

 创建com.xiaonan.service的包

 我们想让一个类加载成bean,还需要加上@Component("")注解,同理将注解创建出来

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    String value();// 表示bean的名字
}

这边也给大家总结了,Target注解定义的所有参数,所表示的意思

ElementType.ANNOTATION_TYPE:表示注解可以应用于注解上。
ElementType.CONSTRUCTOR:表示注解可以应用于构造函数上。
ElementType.FIELD:表示注解可以应用于字段(成员变量)上。
ElementType.LOCAL_VARIABLE:表示注解可以应用于局部变量上。
ElementType.METHOD:表示注解可以应用于方法上。
ElementType.PACKAGE:表示注解可以应用于包上。
ElementType.PARAMETER:表示注解可以应用于方法参数上。
ElementType.TYPE:表示注解可以应用于类、接口、枚举等类型上。

 然后我们就在OrderService类上加上注解@Component("orderService"),把其加载成bean,现在我们的spring就能扫描到这个类了吗?当然不是

扫描逻辑实现

现在就在XiaoNanApplicationContext类中,定义扫描的逻辑

我们定义一个scan()的方法

private void scan() {
        //判断是否存在组件扫描路径这个注解
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            //通过反射方法:getDeclaredAnnotation获取这个注解
            ComponentScan componentScan = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
            if (componentScan == null)
                throw new NullPointerException();
            //获取扫描路径
            String path = componentScan.value();//com.xiaonan.service
            // 获取自定义类加载器
            ClassLoader classLoader = XiaoNanApplicationContext.class.getClassLoader();
            // 首先需要将path中的.转化为/
            path = path.replace(".", "/");
            //从类路径(Classpath)中查找并获取指定名称的资源。
            URL url = classLoader.getResource(path);
            //转化为文件
            File file = new File(url.getFile());
            //如果该文件是目录的话
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                //遍历目录里的所有文件
                for (File f : files) {
                    //获取文件的绝对路径
                    String className = f.getAbsolutePath();
                    //判断是否是.class文件
                    if (className.endsWith(".class")) {
                        className = className.substring(className.indexOf("com"), className.indexOf(".class"));
                        //要将路径中的\转换成.
                        className = className.replace("\\", ".");
                        try {
                            //通过自定义类加载器,加载指定的类
                            Class<?> aClass = classLoader.loadClass(className);

                            //判断该类是否有Component注解,也就是是否注册成bean
                            if (aClass.isAnnotationPresent(Component.class)) {
                                //表示当前这个类是一个Bean对象

                                //获取该类的component注解对象
                                Component component = aClass.getDeclaredAnnotation(Component.class);
                                String beanName = component.value();//拿到bean的名字
                                //创建bean的定义
                                BeanDefinition beanDefinition = new BeanDefinition();
                                //写入bean的类型
                                beanDefinition.setClazz(aClass);
                                // 解析类,判断当前bean是单例bean,还是prototype的bean
                                if (aClass.isAnnotationPresent(Scope.class)) {
                                    //如果存在这个注解
                                    Scope scope = aClass.getDeclaredAnnotation(Scope.class);
                                    beanDefinition.setScope(scope.value());
                                } else {
                                    //没有设置,默认就是单例
                                    beanDefinition.setScope("Singleton");
                                }
                                //将该bean的定义存入bean定义池中
                                beanDefinitionObjects.put(beanName, beanDefinition);
                            }
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

 

有关Java中的类加载器,有三种,这边补充一下:

启动类加载器(Bootstrap ClassLoader):
这是 Java 虚拟机的一部分,它负责加载 Java 核心类库(JDK 中的 rt.jar 等)。
启动类加载器是最顶层的类加载器,它是虚拟机实现的一部分,通常使用 C++ 实现,并不是一个普通的 Java 类。
扩展类加载器(Extension ClassLoader):
也称为扩展类加载器,负责加载 Java 虚拟机的扩展目录(JDK 中的 ext 目录)中的类库。
它是由 Java 实现的,是 sun.misc.Launcher$ExtClassLoader 类的实例。
应用程序类加载器(Application ClassLoader):
也称为系统类加载器,负责加载应用程序的类路径(Classpath)下的类库。
它是由 Java 实现的,是 sun.misc.Launcher$AppClassLoader 类的实例。

 第三种也就是我们最常用的应用程序类加载器

 我们注册的bean一般有两种类型,一种是单例(Singleton)Bean,另一种是原型(Prototype) Bean

  • 单例 Bean 在容器启动时创建,并且只创建一个实例。在整个应用程序周期内,都会使用同一个单例实例。
  • 原型 Bean 在每次被请求时创建新的实例。每次从容器中获取原型 Bean 时,都会创建一个新的对象实例。

我们这里定义一个@Scope注解,默认没有加这个注解就是单例bean

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {
    //Prototype 原型Bean  Singleton 单例
    String value();
}

我们还需要创建一个Bean的定义类,BeanDefinition,用于描述bean的类型,以及是单例还是prototype

package com.spring;

/**
 * bean的定义
 */
public class BeanDefinition {
    private Class clazz;//这个BeanDefinition的类型
    private String scope;//是单例还是prototype

    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }
}

我们会在XiaoNanApplicationContext类中定义一个,存储Bean的定义的集合

    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();//实现了BeanPostProcessor接口的bean

我们现在就基本实现了,bean的扫描,下期再见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值