Spirng 痛苦源码学习(二)——手写spring大致总框架(一)

news/2024/4/25 23:41:40/文章来源:https://blog.csdn.net/weixin_46643875/article/details/128101564

文章目录

  • 前言
  • 一、总体步骤如下
    • 1、spring 文件夹
    • 2、myProject 文件夹
  • 二、主要coding
    • 1、配置文件
    • 2、容器
    • 3、一些spring中的重要的注解
    • 4、项目中的使用
    • 5.重要的bean定义信息
    • 6、postProcessor重要,前置通知和后置
  • 主要项目的截图


前言

本文主要基于spring的注解的方式完成spring总体流程

  • 1、配置文件配合容器实现
  • 2、通过注解扫描所有的bean定义信息
  • 3、完成CreatBean方法
  • 4、实现getBean方法
  • 5、实现postProcessor完成AOP(使用JDK动态代理)
  • 6、实现aware回调

一、总体步骤如下

总的来说
1、使用了spring文件夹模拟了我们平常的依赖。
2、project文件夹模拟我们平常的项目。

1、spring 文件夹

简介=》用于模拟我们导的spring依赖

  • 1、编写一个容器

  • 2、简单的完成一个构造方法,支持传入配置文件,通过注解方式进行下面的操作
    ——以上第一步完成存取——

  • 3、定义一个@ComponentScan注解,目的是为了扫描

  • 4、在context容器构造器中,扫描配置文件的注解,得到其中的路径值

  • 5、扫描对应路径下的所有的类(我们扫描的是.class文件,而不是.java);所以我们要拿到的是应用类加载器,然后获取对应的路径下的文件

——以上是类扫描——

  • 6、获取所有文件的绝对路径,然后截取成可以读取到对应类的值
  • 7、读取对应的类上的注解(如@Component,@Service…)判断是不是一个bean

——以上是判断是不是一个bean的逻辑——

  • 8、定义@Scope注解(定义这是一个单例还是一个原型)
  • 9、判断是不是有@Scope注解,取到其中的值
  • 10、将类信息,单例or原型等信息封装成一个bean的定义信息,放到map中

——以上扫描包结束,封装bean定义信息放map中——

  • 11、根据bean定义信息,创建bean,然后放到singletonObjects中

——以上创建bean到单例池中——

  • 12、定义@Autowired依赖注入注解

  • 13、判断bean类中是不是有对应的属性值有依赖注入注解

  • 14、有点话就先getBean去单例缓存池中拿,拿不到就CreatBean(判断单例和原型)
    ——以上是依赖注入的过程——

  • 15、定义后置处理器接口:里面主要分为前置和后置处理postProcessBeforeInitialization、postProcessAfterInitialization

  • 16、在扫描的时候,就把所有的后置处理器干到缓存池中

  • 17、判断这个bean是不是PostProcess(instanceof 父类就行)

  • 18、在创建bean的时候我们要取出所有缓存中的处理器,然后看看他们要干嘛(循环每一个后置处理器)
    ——以上定义后置处理器接口——

  • 19、定义@XXAware回调接口

  • 20、和后置处理器差不多,我们也才creatBean判断bean是不是Aware

  • 21、有的话就让spring回调干点事情

2、myProject 文件夹

简介=》用于我们项目中利用spring

  • 1、编写配置文件
  • 2、编写启动类
    • 2.1 获取一个容器,然后在容器中getBean

——以上第一步完成存取——

  • 3、和spring配置一样,我们要在配置文件中配置@ComponentScan注解

——以上是类扫描——

  • 4、在对应的类中添加@Scope注解

——以上扫描包结束,封装bean定义信息放map中(区分原型还是单例)——

  • 5、在对应类中添加@Autowired注解即可,其他交给容器去整
    ——以上是依赖注入的过程——

  • 6、自定义一个BeanPostProcess,我们的Aop其实就是这里搞起来的,读取aop的注解缓存池,然后一通前置通知get
    ——以上定义后置处理器接口——

二、主要coding

1、配置文件

package com.xusj.myProject.config;import spring.annotation.ComponentScan;/*** @author xusj* <br>CreateDate 2022/11/29 0:30*/
@ComponentScan("com.xusj.myProject")
public class AppConfig {}

2、容器

package spring.context;import spring.annotation.Autowired;
import spring.annotation.Component;
import spring.annotation.ComponentScan;
import spring.annotation.Scope;import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;/*** @author xusj* <br>CreateDate 2022/11/29 0:29*/
public class MyApplicationContext {private Class<?> configClass;/*** bean定义信息的缓存池* k*/private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();private final Map<String, Object> singletonObjects = new HashMap<>();/*** 构造方法,通过配置类完成** @param clazz*/public MyApplicationContext(Class<?> clazz) {this.configClass = clazz;// 扫描对应下的所有子包// 1、判断有没有注解if (clazz.isAnnotationPresent(ComponentScan.class)) {// 2、先拿到注解和对应的值(路径)ComponentScan componentScanAnnotation = clazz.getAnnotation(ComponentScan.class);String path = componentScanAnnotation.value();// 转化成我们需要的pathpath = path.replace(".", "/");// 3、获取对应的类加载器,我们需要读取的应用类加载器中的文件而不是我们写的.java文件[返回类的类装载器。]ClassLoader classLoader = MyApplicationContext.class.getClassLoader();// 4、获取对应路径的资源URL resource = classLoader.getResource(path);// 5、获取对应位置的文件File file = new File(resource.getFile());// 判断文件if (file.isDirectory()) {getFile(file);}}}/**** 获取所有的文件** @param file 文件* @author xusj* <br>CreateDate 2022/11/29 1:05*/private void getFile(File file) {// 遍历文件(一直遍历到最后)for (File f : Objects.requireNonNull(file.listFiles())) {if (f.isDirectory()) {// 递归getFile(f);} else {// 扫描所有类判断是不是个bean,是的话就创建一个bean定义信息,然后放到map中scanComponent(f);}}// 扫描bean定义信息map缓存,创建beanfor (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {String beanName = entry.getKey();BeanDefinition beanDefinition = entry.getValue();// 判断是单例还是原型if ("singleton".equals(beanDefinition.getScope())) {// 创建beanObject bean = createBean(beanName, beanDefinition);// TODO XUSJ 放到单例池中singletonObjects.put(beanName, bean);}}}/*** 创建bean** @param beanName* @param beanDefinition*/public Object createBean(String beanName, BeanDefinition beanDefinition) {// 获取类信息Class<?> clazz = beanDefinition.getType();// 通过构造方法创建beanObject instance = null;try {instance = clazz.getConstructor().newInstance();// 判断依赖注入的问题for (Field field : clazz.getDeclaredFields()) {// 判断该类中有没有@Autowiredif (field.isAnnotationPresent(Autowired.class)) {field.setAccessible(true);field.set(instance, getBean(field.getName()));}}} catch (Exception e) {e.printStackTrace();}return instance;}private void scanComponent(File f) {// 1 获取所有的文件// 获取文件的绝对路径String absolutePath = f.getAbsolutePath();// 更换成可应用类加载器可以处理的结果absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));absolutePath = absolutePath.replace("\\", ".");// 2 通过应用类加载器获取绝对路径中的类System.out.println("对应文件的绝对路径=》" + f.getName() + ":" + absolutePath);ClassLoader classLoader = MyApplicationContext.class.getClassLoader();try {Class<?> clazz = classLoader.loadClass(absolutePath);// 3 判断这个类上面是不是有对应的bean相关的注解if (clazz.isAnnotationPresent(Component.class)) {// 4 获取我们定义的beanNameComponent componentScanAnnotation = clazz.getAnnotation(Component.class);String beanName = componentScanAnnotation.value();// 如果没有定义的话,直接就使用类目作为beanNameif (beanName.isEmpty()) {// 获取简单类目beanName = Introspector.decapitalize(clazz.getSimpleName());}// 5 构建bean的定义信息BeanDefinition beanDefinition = new BeanDefinition();beanDefinition.setType(clazz);beanDefinition.setBeanName(beanName);// 6、通过注解判断是单例还是原型if (clazz.isAnnotationPresent(Scope.class)) {Scope scopeAnnotation = clazz.getAnnotation(Scope.class);String value = scopeAnnotation.value();// 放到bean的定义信息中beanDefinition.setScope(value);} else {// 默认为singletonbeanDefinition.setBeanName("singleton");}// 将bean定义信息,放到beanDefinitionMap中beanDefinitionMap.put(beanName, beanDefinition);}} catch (ClassNotFoundException e) {e.printStackTrace();}}/**** 通过beanName去获取bean** @param name beanName* @return {@link Object}* @author xusj* <br>CreateDate 2022/11/29 0:34*/public Object getBean(String name) {// 先判断bean定义信息是不是有值if (!beanDefinitionMap.containsKey(name)) {throw new RuntimeException();}// 获取bean定义信息BeanDefinition beanDefinition = beanDefinitionMap.get(name);// 判断是不是单例if ("singleton".equals(beanDefinition.getScope())) {// 先去单例池中去拿Object singletonObj = singletonObjects.get(name);if (singletonObj == null) {// 没有说明还没创建,那我就创建一个singletonObj = createBean(name, beanDefinition);// 放到单例池中singletonObjects.put(name, singletonObj);}return singletonObj;} else {// 原型模式[就是直接搞一个新的出来]Object prototypeBean = createBean(name, beanDefinition);return prototypeBean;}}
}

3、一些spring中的重要的注解

在这里插入图片描述

4、项目中的使用

package com.xusj.myProject.service;import spring.annotation.Autowired;
import spring.annotation.Component;
import spring.annotation.Scope;
import spring.aware.MyBeanAware;/*** @author xusj* <br>CreateDate 2022/11/29 0:35*/
@Component(value = "oneService")
@Scope(value = "singleton")
public class OneService implements MyBeanAware {@Autowiredprivate TwoService twoService;public void test() {System.out.println("twoService" + "+++" + twoService);}@Overridepublic void setSth() {}
}

5.重要的bean定义信息

package spring.context;/*** @author xusj* <br>CreateDate 2022/11/29 10:02*/
public class BeanDefinition {/*** beanName*/private String beanName;/*** 类信息*/private Class<?> type;/*** bean类型*/private String scope;public BeanDefinition() {}@Overridepublic String toString() {return "BeanDefinition{" +"beanName='" + beanName + '\'' +", type=" + type +", scope='" + scope + '\'' +'}';}public String getScope() {return scope;}public void setScope(String scope) {this.scope = scope;}public String getBeanName() {return beanName;}public void setBeanName(String beanName) {this.beanName = beanName;}public Class<?> getType() {return type;}public void setType(Class<?> type) {this.type = type;}}

6、postProcessor重要,前置通知和后置

依赖倒置原则,先定义接口

package com.xusj.myProject.postProcessor;import spring.annotation.Component;
import spring.processor.BeanPostProcessor;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** 我的后置处理器** @author xusj* <br>CreateDate 2022/11/29 16:31*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {// 前置搞点事情(Aop,这里用的是JDK的动态代理,spring使用的CGLIB)@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {// 其实AOP也是在这里整的,就是读一些注解然后整if ("userService".equals(beanName)) {return Proxy.newProxyInstance(MyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 切面System.out.println("切面逻辑");// 原来的对象去执行方法return method.invoke(bean, args);}});}// beanreturn bean;}// 后置搞点事情@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);}
}

7、aware回调接口
总的来说和postProecssor一样,也是依赖倒置原则

主要项目的截图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_38859.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python的10个编程技巧,你不一定都知道

文末有Python资料下载 1. 原地交换两个数字 Python 提供了一个直观的在一行代码中赋值与交换&#xff08;变量值&#xff09;的方法&#xff0c;请参见下面的示例&#xff1a; 赋值的右侧形成了一个新的元组&#xff0c;左侧立即解析&#xff08;unpack&#xff09;那个&…

leetcode-每日一题-1758-生成交替二进制字符串的最少操作数(简单,数学思想)

这道题标记为简单题是正常的&#xff0c;因为当你想到0或者1开头的时候就已经结束了看看我的分析 那么知道这个信息之后就很简单了&#xff0c;加上我们的位运算符号^作为标记即可&#xff0c;大家看看代码实现 1758. 生成交替二进制字符串的最少操作数 难度简单88收藏分享切换…

Mysql基础知识篇(二)

1.UNION 与 UNION ALL 的区别&#xff1f; 如果使用 UNION&#xff0c;会在表链接后筛选掉重复的记录行如果使用 UNION ALL&#xff0c;不会合并重复的记录行从效率上说&#xff0c;UNION ALL 要比 UNION 快很多&#xff0c;如果合并没有刻意要删除重复行&#xff0c;那么就使…

Java基于springboot+vue的摄影作品展示交流系统 计算机毕业设计

随着时代的发展&#xff0c;人们的精神世界也在不断的丰富&#xff0c;尤其是在当下电子设备发展迅速的背景下&#xff0c;人们通过数码相机或者手机随后就可以拍下每一个美丽的瞬间&#xff0c;但是人们更希望将这些摄影作品传到网上和更多的人进行分享&#xff0c;同时也希望…

电脑键盘功能基础知识,快速入门,抓住这份详细教程

在互联网生活发达的今天&#xff0c;电脑已经成为了学习工作的必备工具。而用来操作电脑的关键&#xff0c;就是我们经常使用的键盘和鼠标。最近有不少的小伙伴来私信小编&#xff0c;希望小编做一个电脑键盘功能基础知识介绍的详细教程。这不&#xff0c;小编应大家要求&#…

用匠心创造可期未来!与广州流辰信息科技一起携手创佳绩!

当今社会世界经济一体化趋势逐渐明朗化&#xff0c;竞争也愈发激烈&#xff0c;同时&#xff0c;这也是一个机遇与挑战并存的开放社会。在机遇面前&#xff0c;作为企业&#xff0c;要紧紧抓住机遇&#xff0c;顺势而为&#xff0c;创造辉煌佳绩&#xff1b;在挑战面前&#xf…

嵌入式 C语言/C++ 常见笔试、面试题 难疑点汇总(经典100道)

#pragma comment。将一个注释记录放置到对象文件或可执行文件中。 #pragma pack。用来改变编译器的字节对齐方式。 #pragma code_seg。它能够设置程序中的函数在obj文件中所在的代码段。如果未指定参数&#xff0c;函数将放置在默认代码段.text中 #pragma once。保证所在文件只…

MySQL软件常见操作

1登录MySQL 登录&#xff0c;如果你配置了环境变量就可以winr&#xff0c;在运行框输入cmd&#xff0c;输入登录命令 第一种&#xff1a;直接输入密码 mysql -uroot -p(你的密码没有有括号) 第二种不直接输入密码 mysql -uroot -p 前面两种都是localhost登录 下面是完整版 m…

slam定位学习笔记(七)-g2o学习

主要学习的是这篇文章&#xff0c;但大佬并没有在文章里面仔细的讲g2o&#xff0c;所以我在网上找了这几篇介绍g2o的文章&#xff0c;讲的十分详细&#xff0c;对入门十分友好&#xff1a;文章一、文章二、文章三&#xff0c;这三篇都是一个作者写的&#xff0c;主要是针对编程…

MATLAB数据导入

MATLAB数据导入 在编写一个程序时&#xff0c;经常需要从外部读入数据。MATLAB使用多种格式打开数据。本章将要介绍MATLAB中数据的导入。 MATLAB中导入数据的方式有两种&#xff0c;分别是在命令行通过代码把数据导进去和通过MATLAB的数据导入向导导入数据。本节将为大家介绍第…

广播实现强制下线功能

实现强制下线功能 强制下线应该是一个比较常用的功能,比如QQ在比的地方被登陆了,就会强制比被挤下线.强制下线的功能还是比较简单的,只需要在界面上弹出一个框,告知用户无法再进行任何操作即可.只能点击确定然后跳转至登录界面.强制下线功能需要关闭所有的Activity,然后返回到…

微服务框架 SpringCloud微服务架构 4 Ribbon 4.3 饥饿加载

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构4 Ribbon4.3 饥饿加载4.3.1 饥饿加载4.3.2 总结4 Ribbon 4.3 饥饿加载 4…

【毕业设计】深度学习车辆颜色识别检测系统 - python opencv YOLOv5

文章目录1 前言2 实现效果3 CNN卷积神经网络4 Yolov55 数据集处理及模型训练6 最后1 前言 &#x1f525; Hi&#xff0c;大家好&#xff0c;这里是丹成学长的毕设系列文章&#xff01; &#x1f525; 对毕设有任何疑问都可以问学长哦! 这两年开始&#xff0c;各个学校对毕设…

ATJ2157ATJ2127音乐按文件名拼音排序---标案是按内码进行排序

音乐按文件名拼音进行排序参考网站ATJ2157&ATJ2127 排序是按照内码(汉字为GBK即GBK936)排序的按拼音排序unicode与拼音的对比表(U2P.DAT)&#xff0c;需要打包到固件中U2P.DAT数据结构U2P.DAT生成代码是使用DEV-C生成其他说明U2P.DAT与ATJ2127平台代码参考网站 各种字符对…

activiti-api

activiti-api目录概述需求&#xff1a;设计思路实现思路分析1.VariableEvent2.EmptyResult3.BPMNElement4.BPMNError5.ConnectorAbstractSecurityManager参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&…

Mac下安装Hadoop

1、引言 如果想在Mac下安装Hadoop而且让Hadoop能正常运行&#xff0c;那安装之前需要先安装java&#xff0c;在Mac环境下安装Hadoop。 2、配置ssh环境 在Mac下如果想使用Hadoop&#xff0c;必须要配置ssh环境&#xff0c; 如果不执行这一步&#xff0c;后面启动hadoop时会出现…

PyCharm+PyQT5之三界面与逻辑的分离

之二的例程已经实现了界面与逻辑的分离,所建立的 Dialog Mainwindow 或者 widgets 等,界面改变其主调程序(暂且这样叫)更改,或者不需要大规模更改, 主调函数的程序是这样的 import sys import FistUI from PyQt5.QtWidgets import QApplication, QMainWindow,QDialog if __nam…

解决 Android WebView 多进程导致App崩溃

应用场景 应用内有两个位置用到WebView加载页面&#xff0c;具体处理逻辑不能通用。分别扩展了WebView了。应用内独立页面使用Fragment来展示,(采用单Activity架构&#xff09;。应用提供切换语言功能。 问题猜想 一、WebView内核bug 具体路径&#xff1a; 进入app–>设…

cmake使用

1. cmake概述及例子 CMake快速入门 cmake、qmake、cl之间关系 1.1 各种cmake cmake根据CMakeLists.txt生成makefile&#xff0c;make根据makefile行编译。 1.1.1 最简cmake&#xff1a;生成可执行程序&#xff08;一个文件&#xff09; #CMakeLists.txt cmake_minimum_req…

debug - JLX12864C(ST7920-12864)液晶屏不能使用串行通讯的原因

文章目录debug - JLX12864C(ST7920-12864)液晶屏不能使用串行通讯的原因概述调试备注ENDdebug - JLX12864C(ST7920-12864)液晶屏不能使用串行通讯的原因 概述 正在给板子写出厂测试程序, 买的12864型号是JLX12864C. STC官方给的例程是并行通讯, 好使. 但是想在测试程序中改为…