大家好,今天我想和大家分享一个令人激动的话题——手写框架!随着技术的不断发展,我们对各种流行框架的使用已经司空见惯。但是,你是否想过框架背后的原理和工作原理?我相信很多人都曾经有过一种冲动,想要亲手实现一个属于自己的框架。
在这个系列的博客中,我将带领大家一起探索手写框架的奇妙之处。我们将以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的扫描,下期再见!