手写一个Spring IOC框架

server/2024/9/23 20:41:20/

目录

一,Spring IOC

 二,流程图设计

三,设计思路解析

三,开始写代码

1.准备工作:

2.扫描并加载类信息

3.初始化bean

4.测试一下


一,Spring IOC

Spring IoC容器是Spring框架的核心,它通过读取配置信息来自动创建对象(bean)、配置对象属性以及管理对象的生命周期。IoC容器利用依赖注入(DI)自动将对象的依赖关系注入到需要它们的其他对象中,从而减少代码间的耦合度。

容器支持多种bean作用域和生命周期管理,提供了事件处理、国际化消息和资源访问等高级功能。此外,Spring IoC与AOP(面向切面编程)紧密结合,支持通过切面来实现如日志、事务等横切关注点的处理。

上面是IOC的介绍,接下来我将去完成一个简单的IOC容器,剖析底层。

 二,流程图设计

三,设计思路解析

  1. 扫描类路径下所有以Java结尾的文件: 在Spring框架中,通过使用ClassPathScanningCandidateComponentProvider类,可以扫描指定的类路径下所有的.class文件。这个类通常与AnnotationConfigApplicationContext一起使用,后者是Spring中用于处理注解配置的上下文。

  2. 获得需要被IoC管理的类: 通过类路径扫描,Spring容器会筛选出那些需要被IoC容器管理的类。这些类通常带有特定的注解,如@Component@Service@Repository@Controller等,这些注解表明了一个类将作为Spring容器中的一个bean。

  3. 全类名加入到beanNames集合中: 将筛选出的类全限定名(即包名+类名)加入到一个名为beanNames的集合中。这个集合是Spring容器内部用于跟踪所有候选bean的一个列表。

  4. 反射为实例属性赋值: 对于每个候选的bean类,Spring容器会通过反射创建其实例,并通过反射机制为其属性赋值。这一过程涉及到处理@Autowired注解,以及根据类型或名称自动装配依赖关系。

  5. 完成依赖注入的过程: 依赖注入是Spring IoC容器的核心功能之一。Spring会根据bean的定义,解析和注入所有需要的依赖。这可能涉及到查找其他bean、处理复杂的依赖关系(如循环依赖)以及使用BeanFactory来获取和注入依赖。

  6. 反射获取类上的注解: 在处理每个bean时,Spring容器会通过反射获取类上的注解信息。这些注解可能会影响bean的创建、作用域、生命周期等。例如,@Scope注解定义了bean的作用域,@PostConstruct注解指定了在构造之后执行的方法等。

  7. 容器启动: 在所有bean都被创建并注入依赖之后,Spring容器会启动。这通常涉及到调用ApplicationContextrefresh()方法,该方法会触发容器的启动过程,包括初始化所有的singleton beans、处理@PostConstruct注解的方法、以及发送ContextRefreshedEvent事件。

  8. 启动结束: 一旦容器启动完成,Spring会发送一个ContextClosedEvent事件,表明容器已经准备就绪,可以开始处理请求和执行业务逻辑。

三,开始写代码

我们将一步步按照流程图中所述的将IOC底层实现出来。

1.准备工作

编写注解和测试类(包含@Autowired和@Component)

java">import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
java">@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
java">@Component
public class UserService {public void addUser(String name, int age) {System.out.println(name);System.out.println(age);}
}
java">@Component
public class TestController {@Autowiredprivate UserService userService;public void test(){userService.addUser("zhangsan",18);}
}

2.扫描并加载类信息

(1)扫描类路径下的所有文件将其加入到集合中。

(2)将.java结尾的文件筛选出来

java">    //构造函数public SpringIOC() {initPath();try {scan();} catch (FileNotFoundException e) {e.printStackTrace();}beanNames= new ArrayList<>();initBeanNames();}private void initPath(){basePath="类路径自己指定";basePackage="自己指定扫描包";}/*** 将类路径下所有文件提取出来放到集合中*/private void scan() throws FileNotFoundException {File file = new File(basePath);filePaths = new ArrayList<>();if(file.exists()){Queue<File> queue = new LinkedList<>();queue.add(file);while(!queue.isEmpty()){File poll = queue.poll();if(poll == null){continue;}if(poll.isDirectory()){//目录下面的所有文件夹File[] files = poll.listFiles();for (File f : files) {queue.add(f);}}else {filePaths.add(poll.getPath());//将单个文件放到filePaths当中}}}else {throw new FileNotFoundException(basePath+" not found");}}/*** 将所有的.java结尾的 全限定名放到 beanNames*/public void  initBeanNames(){for (String s : filePaths) {//遍历刚才文件路径String replace = s.replace(basePath, "");if(replace.endsWith(".java")) {replace = replace.substring(0, replace.length()-5);}char[] chars = replace.toCharArray();for (int i = 0; i < chars.length; i++) {if(chars[i]=='\\'){chars[i] = '.';}}beanNames.add(basePackage+"."+new String(chars));}}

3.初始化bean

在2中我们已经得到了所有java类的类名,这一步可以根据全类名来获取类的注解等各种信息。

(1)反射获取类上注解,筛选出被IOC管理的类并创建实例,封装到ioc容器中。

(2)为ioc容器中的bean反射设置属性(依赖注入)

java">    //非懒加载public void initBeans(){for (String beanName : beanNames) {System.out.println(beanName);try {Class<?> aClass = Class.forName(beanName);Annotation[] declaredAnnotations = aClass.getDeclaredAnnotations();//获取类上的所有注解for (Annotation declaredAnnotation : declaredAnnotations) {if(declaredAnnotation instanceof Component){//反射获取带@Component的类Object o = aClass.newInstance();//创建实例beans.put(aClass.getName(),o);//将类名-实例放到beans容器当中}}} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {e.printStackTrace();}}System.out.println(beans);for (Map.Entry<String, Object> entry : beans.entrySet()) {//将实例从map中拿出来Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();//获取实例的所有字段for (Field field : declaredFields) {Annotation[] declaredAnnotations = field.getDeclaredAnnotations();//字段上所有的注解for (Annotation annotation : declaredAnnotations) {if (annotation instanceof Autowired) {//带@Autowired的注解String name = field.getType().getName();//com.heaboy.springioc.entity.UserService// 从beans 中获得对应的对象Object o = beans.get(name);//beans是个map 只要里面有这样的类型 就能拿到field.setAccessible(true);//设置可访问的权限try {field.set(entry.getValue(), o);//反射将o设置给字段} catch (IllegalAccessException e) {e.printStackTrace();}}}}}}

4.测试一下

3完成之后,此时加@Autowired和@Component注解的类已经在集合中了,属性也已经注入。我们来编写测试代码debug一下。

java">public class SpringIOCTest {@Testpublic void testScan() throws FileNotFoundException {SpringIOC springIOC = new SpringIOC();//获取java类的全类名springIOC.initBeans();TestController instance = (TestController)springIOC.getInstance(TestController.class.getName());instance.test();}
}

可以看到两个bean实例以及他们之间的依赖关系。

完整代码获取可以去我的git: https://gitee.com/code0321/ioc

拜托可以给它点点赞哦~


http://www.ppmy.cn/server/15164.html

相关文章

babylonjs Web3D模型载入报错解决

报错1:BJS - [14:23:17]: Unable to find a plugin to load .glb files. Trying to use .babylon default plugin. To load from a specific filetype (eg. gltf) see: https://doc.babylonjs.com/features/featuresDeepDive/importers/loadingFileTypes 报错2:BJS - [14:11:48…

elementui单个输入框回车刷新整个页面

<!-- 搜索 --> <el-form :model"queryParams" ref"queryForm" :inline"true"><el-form-item label"名称" prop"nameLike"><el-input v-model"queryParams.nameLike" placeholder"请输入…

【二叉树】第二章:实现二叉树相关函数,感受递归分治的魅力

&#x1f60e;感受递归分治的魅力吧&#xff01; 本章节一共可以实现下面几个功能函数&#xff1a; 求 Size 二叉树节点个数、 Height 树的高度、 KNum 第k层节点个数、 Find 查找节点、 Leaves 叶子节点个数、 Destroy 销毁 前言&#xff1a;本章节函数的实现有些会借助到 前…

【鸿蒙开发】闪屏页面练习

1. 创建页面 Index.ets Entry Component struct Index {build() {Column() {Text("首页").fontSize(50).fontWeight(FontWeight.Bold)}.width(100%).height(100%)} }2. 创建页面 SplashScreen.ets Entry Component struct SplashScreen {State message: string Sp…

使用微信开发者工具模拟微信小程序定位

哈喽&#xff0c;各位同僚们&#xff0c;我们平时在测试微信小程序的时候&#xff0c;如果小程序中有获取定位或者地图的功能&#xff0c;测试场景中常常需要去模拟不同的位置&#xff0c;例如我们模拟在电子围栏的外面、里面和边界区域等。那么&#xff0c;我们如何在模拟微信…

python数字验证码自动识别

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 在网络上&#xff0c;许多网站和应用程序使用验证码&#xff08;Completely Automated Publ…

MySql CPU激增原因分析

QPS激增会导致CPU占用升高 分析 可以使用监控工具&#xff0c;查看CPU利用率曲线图和QPS曲线图进行对比。如果CPU曲线图波动情况跟QPS曲线图波动情况基本保持一致&#xff0c;可以明确明确CPU升高时QPS上升导致。反之&#xff0c;CPU曲线图对比QPS曲线图有不同步的峰值抖动&am…

【教程】MySQL数据库学习笔记(五)——约束(持续更新)

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【MySQL数据库学习】系列文章 第一章 《认识与环境搭建》 第二章 《数据类型》 第三章 《数据定义语言DDL》 第四章 《数据操…