手写Spring框架

手写Spring框架

  • 准备工作
  • Spring启动和扫描逻辑实现
  • 依赖注入的实现
  • Aware回调模拟实现和初始化机制模拟实现
  • BeanPostProcessor (Bean的后置处理器) 模拟实现
  • Spring AOP 模拟实现
  • Spring Bean生命周期
    • 源码分析
  • Spring中两种生成代理的方式
    • 题外话
  • Spring事务相关
    • Spring事务传播机制
    • Spring事务的隔离级别

准备工作

  1. 准备一个空的工程
  2. 创建spring的容器类,它是Spring IOC理念的实现,负责对象的实例化、对象和对象之间依赖关系配置、对象的销毁、对外提供对象的查找等操作,对象的整个生命周期都是由容器来控制。传统使用方法是传入一个spring的配置文件或配置类根据用户的配置来创建这个容器。
package com.spring;public class EditApplicationContext {//传入配置类private Class configClass;public EditApplicationContext(Class configClass) {this.configClass = configClass;}//定义根据别名获取类的方法public Object getBean(String name){return null;}
}
  1. 定义一个配置类,相当于配置文件
package com.zedit;import com.spring.ComponentScan;//指定包扫描路径
@ComponentScan("com.zedit.service")
public class AppConfig {
}
- 如何定义包扫描路径,编写一个注解类
@Retention(RetentionPolicy.RUNTIME)
//规定只能写在类上
@Target(ElementType.TYPE)
public @interface ComponentScan {//接收属性值,指定扫描路径String value() default "";
}
  1. 定义一个Component注解,它的作用就是将类交给spring容器,实现bean的注入
@Retention(RetentionPolicy.RUNTIME)
//规定只能写在类上
@Target(ElementType.TYPE)
public @interface Component {//提供默认值String value() default "";
}

Spring启动和扫描逻辑实现

  1. 传入配置类对于spring而言 它只需要判断配置类有没有它提供的注解,获取扫描路径值,根据路径值
  2. 通过类加载器加载目录下的类,首先获取所有文件,然后获取全限定类名
public EditApplicationContext(Class configClass) {this.configClass = configClass;//解析配置类//Component注解->扫描路径->扫描ComponentScan declaredAnnotation = (ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);String path = declaredAnnotation.value();// 全限定类名加工成能用的路径名 "com/xuhua/service"path = path.replace(".", "/");ClassLoader classLoader = EditApplicationContext.class.getClassLoader();//根据AppClassLoader加载器目录获取 classPath目录下中的‘path’目录下的资源URL resource = classLoader.getResource(path);//判断是否是文件夹而不是单个文件if (file.isDirectory()) {File[] files = file.listFiles();for (File f : files) {String fileName = f.getAbsolutePath();// /Users/zhuxuhua/Desktop/project/spring-edit/target/classes/com/zedit/service/XxxUtils.class// 转换成 com.zedit.service.XxxUtilsif (fileName.endsWith(".class")) {String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));className = className.replace("/", ".");try {//根据全限定类名加载类Class<?> clazz = classLoader.loadClass(className);//判断扫描到的类是不是一个bean注解if (clazz.isAnnotationPresent(Component.class)){}} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}}}
}
  1. 根据@Scope 注解判断bean是单例还是原型
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {String value();
}
  1. 定义单例池
    在这里插入图片描述
  2. 由于在使用bean和初始化bean时都要去解析bean的定义与他的注解,如果不做设计每次的解析就会显得冗余繁琐,所以spring在Context扫描阶段定义了一个BeanDefinition定义类,它记录了bean的各种信息,先将扫描到的bean填入BeanDefinitionMap随后处理单例对象

//存储单例对象的单例池
private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();
//存储所有bean的定义
private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
-------
try {Class<?> clazz = classLoader.loadClass(className);if (clazz.isAnnotationPresent(Component.class)) {//表示当前这个类有Component注解是一个bean对象//解析类,判断scope注解是单例的bean还是 prototype的bean//每扫描到一个bean就定义一个BeanDefinition对象Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);String beanName = componentAnnotation.value();BeanDefinition beanDefinition = new BeanDefinition();//spring bean默认为多例模式beanDefinition.setScpoe("prototype");if (clazz.isAnnotationPresent(Scope.class)){Scope annotation = clazz.getAnnotation(Scope.class);String value = annotation.value();if (value.equals("singleton")){beanDefinition.setScpoe("singleton");}}beanDefinition.setClazz(clazz);//扫描到的所有bean都存入这个mapbeanDefinitionMap.put(beanName,beanDefinition);}} catch (ClassNotFoundException e) {throw new RuntimeException(e);
}
  1. 扫描完后根据存储的beanDefinitionMap填入单例池
    在这里插入图片描述
  2. 获取bean方法中判断是否是单例bean,如果是直接从单例池中取,如果不是则创建bean
public Object getBean(String beanName){//获取bean 如果map中没有就抛出异常,说明她不是一个bean,没有被扫描到if (beanDefinitionMap.containsKey(beanName)){BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);//判断scope值,单例直接从单例池中取if (beanDefinition.getScpoe().equals("singleton")){return singletonObjects.get(beanName);}else {//原型bean每次从新创建return createBean(beanDefinition);}}else {throw new NullPointerException();}}//用beanDefinition中的clazz信息通过反射创建bean
public Object createBean(BeanDefinition beanDefinition){Class clazz = beanDefinition.getClazz();try {Object instance = clazz.getDeclaredConstructor().newInstance();return instance;} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}
}

依赖注入的实现

首先注解,能标注在成员变量上

Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {}
@Component("userService")
@Scope("singleton")
public class UserService {@Autowiredprivate OrderService orderService;public void test(){System.out.println(orderService);}
}

依赖注解的实现原理就是在启动扫描初始化阶段 spring创建bean时 给@Autowired的成员变量赋值

//用beanDefinition中的clazz信息通过反射创建beanpublic Object createBean(BeanDefinition beanDefinition){Class clazz = beanDefinition.getClazz();try {Object instance = clazz.getDeclaredConstructor().newInstance();//依赖注入实现原理for (Field declaredField : clazz.getDeclaredFields()) {if (declaredField.isAnnotationPresent(Autowired.class)){Object bean = getBean(declaredField.getName());declaredField.setAccessible(true);declaredField.set(instance,bean);}}return instance;} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}

在这里插入图片描述

Aware回调模拟实现和初始化机制模拟实现

需要回调的实现接口方法,在初始化阶段bean的创建阶段将beanName通过反射设置值

//回调接口
public interface BeanNameAware {void setBeanName(String name);
}-------public interface InitializingBean {void afterPropertySet();
}

@Component("userService")
@Scope("singleton")
public class UserService implements BeanNameAware, InitializingBean {@Autowiredprivate OrderService orderService;private String beanName;@Overridepublic void setBeanName(String name) {beanName = name;}@Overridepublic void afterPropertySet() {System.out.println("初始化");}
//用beanDefinition中的clazz信息通过反射创建beanpublic Object createBean(String beanName,BeanDefinition beanDefinition){Class clazz = beanDefinition.getClazz();try {Object instance = clazz.getDeclaredConstructor().newInstance();//依赖注入for (Field declaredField : clazz.getDeclaredFields()) {if (declaredField.isAnnotationPresent(Autowired.class)){Object bean = getBean(declaredField.getName());declaredField.setAccessible(true);declaredField.set(instance,bean);}}//aware 回调if (instance instanceof BeanNameAware){((BeanNameAware) instance).setBeanName(beanName);}//反射调用初始化bean的方法if (instance instanceof InitializingBean){((InitializingBean) instance).afterPropertySet();}return instance;} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);} catch (InvocationTargetException e) {throw new RuntimeException(e);} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}

BeanPostProcessor (Bean的后置处理器) 模拟实现

spring的扩展机制,在bean初始化前后调用

//定义接口  有初始化前后两种操作,也可以添加更多
public interface BeanPostProcessor {Object postProcessorBeforeInitialization(Object bean,String beanName);Object postProcessorAfterInitialization(Object bean,String beanName);
}----------------//自定义 BeanPostProcessor 实现BeanPostProcessor接口
@Component
public class ZhuZhuBanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessorBeforeInitialization(Object bean, String beanName) {System.out.println("初始化前");//定制操作if (beanName.equals("userService")) {System.out.println("userService 初始化前");}return null;}@Overridepublic Object postProcessorAfterInitialization(Object bean, String beanName) {System.out.println("初始化后");return null;}
}--------//同其他bean一样在扫描时 加载 判断是否实现了BeanPostProcessor,如果实现了就放入 专门的List存储//scan方法中  判断此类是否实现了BeanPostProcessor,并存入list
if (BeanPostProcessor.class.isAssignableFrom(clazz)) {BeanPostProcessor beanPostProcessorInstance = (BeanPostProcessor) clazz.getDeclaredConstructor().newInstance();beanPostProcessorList.add(beanPostProcessorInstance);
}---------//createBean方法中//createBean 时调  初始化前调用   
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {//在调用初始化方法前 重新赋值对象instance = beanPostProcessor.postProcessorBeforeInitialization(instance,beanName);
}//初始化
if (instance instanceof InitializingBean){try {((InitializingBean) instance).afterPropertySet();} catch (Exception e) {throw new RuntimeException(e);}
}//createBean 时调  初始化后调用   
for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {//在调用初始化方法后 重新赋值对象instance = beanPostProcessor.postProcessorAfterInitialization(instance, beanName);
}

Spring AOP 模拟实现

使用jdk动态代理 实现

@Component("userService")
@Scope("singleton")
public class UserServiceImpl implements UserService{@Autowiredprivate OrderService orderService;private String beanName;@Overridepublic void setBeanName(String name) {beanName = name;}@Overridepublic void afterPropertySet() {System.out.println("初始化");}@Overridepublic void test(){System.out.println(orderService+"orderService test");System.out.println(beanName);}}--------public interface UserService {void test();
}

结合 BeanPostProcessor 完成jdk动态的实现

    @Overridepublic Object postProcessorAfterInitialization(Object bean, String beanName) {System.out.println("初始化后");if (beanName.equals("userService")){Object proxyInstance = Proxy.newProxyInstance(ZhuZhuBanPostProcessor.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);}});return proxyInstance;}return bean;}

被动态代理后的类,执行类中的任意方法 都会经过 jdk的代理逻辑进行增强

//测试
public static void main(String[] args) {EditApplicationContext application = new EditApplicationContext(AppConfig.class);//Object bean1 = application.getBean("userService");//Object bean2 = application.getBean("userService");//Object bean3 = application.getBean("userService");//System.out.println(bean1);//System.out.println(bean2);//System.out.println(bean3);UserService userService = (UserService) application.getBean("userService");//userService.test();userService.example();}

Spring Bean生命周期

SpringBean 推断构造方法,Bean中两个或以上有参构造器会报错,
在这里插入图片描述

准备阶段大概生命周期

  1. 实例化

  2. 属性填充

  3. 初始化
    初始化机制,实现Spring提供的 initializingBean 接口 ,在Bean属性填充后自动执行after方法,可以在使用SpringBean容器的情况下做更多操作
    在这里插入图片描述

源码分析

DefaultListableBeanFactory的父类 AbstractAutowireCapableBeanFactory 中 的 doCreateBean 类似 我们上面写scan方法

实例化
在这里插入图片描述
属性填充
在这里插入图片描述
初始化操作
在这里插入图片描述

初始化源码内
在这里插入图片描述

初始化方法中同时判断是否有AOP操作
在这里插入图片描述
判断是否
在这里插入图片描述
判断Bean的类型
在这里插入图片描述
class的类型判断如果是这些类 则不需要AOP
在这里插入图片描述

上面是不做AOP的操作,在Spring中判断要做AOP的操作

  1. 找出当前项目所有切面类型的Bean,如下图的切点Bean,缓存起来

在这里插入图片描述
2. 从缓存的切点Bean中是否有当前Bean匹配的 pointCut 如上图的 @Before , 如果有 则需要AOP

匹配成功 createProxy 创建代理对象 进行 AOP操作
在这里插入图片描述

Spring中两种生成代理的方式

  1. JDK
  2. Cglib,Cglib基于类的继承来实现,Cglib会创建一个类去继承被代理类,从而重写被代理类中的方法
    在这里插入图片描述

题外话

被AOP代理过的类没有进行再次的属性填充
在这里插入图片描述
所以获取到的userService 中的属性都为null,日常使用时,不会去获取 被代理后的类,AOP的目的是在执行Bean的某一个方法时 额外的去执行定义的切点方法,所以被代理的Bean只是在 调用方法时才使用到,真实使用场景中,都是使用的一开始实例化的Bean ,它是有被属性填充的

Spring事务相关

Spring事务传播机制

如此在方法中再次事务方法,a方法的事务注解不会生效,因为此时调用test方法的是没有被aop增强的bean
在这里插入图片描述
示例 target为bean生命周期中 aop之前的bean
在这里插入图片描述
要使事务注解a方法生效,可以通过@Autwired注入bean在调用
在这里插入图片描述

如此直接调用也不会生效
在这里插入图片描述

所以使用事务时,务必使用注入bean来调用事务方法

如此传播级别为 REQUIRES_NEW时 实物方法中再调用事务方法为新建一个事务
在这里插入图片描述

Spring事务的隔离级别

Spring事务的隔离级别和MySQL隔离级别一一对应

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

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

相关文章

C++——栈和队列容器

前言&#xff1a;这篇文章我们将栈和队列两个容器放在一起进行分享&#xff0c;因为这两个要分享的知识较少&#xff0c;而且两者在结构上有很多相似之处&#xff0c;比如栈只能在栈顶操作&#xff0c;队列只能在队头和队尾操作。 不同于前边所分享的三种容器&#xff0c;这篇…

HarmonyOS 应用开发-ArkUI(ets)仿“腾讯新闻”APP

一、效果演示 1、新闻列表页 2、新闻详情页、图片展示页 3、视频页 4、动态页 二、 流程图 –本来自定义了视频的控制栏的&#xff0c;但是发现VideoController()控制器的bug会导致控制器失效&#xff0c;所以没继续做。视频页先不搞了。 三、文件组织&#xff08;“我的页面…

网工内推 | 深信服、宁德时代,最高20K招安全工程师,包吃包住

01 深信服科技 招聘岗位&#xff1a;安全服务工程师 职责描述&#xff1a; 1.负责现场安全服务项目工作内容&#xff0c;包含渗透测试、安全扫描、基线核查、应急响应等&#xff1b; 2.协助用户完成安全测试漏洞整改、复测工作&#xff1b; 3.为用户提供网络、主机、业务系统等…

dg_mmld部分复现

Ours ( K ˆ \^{K} Kˆ2)复现结果– Photo&#xff1a;0.9634730538922156 (at Epoch 23) Art&#xff1a;0.8125 (at Epoch 23) Cartoon&#xff1a;0.7713310580204779 (at Epoch 18) 差距在可接受范围内 辅助信息 If you send 作者 an e-mail, 作者 will tell you a URL w…

2022年蓝桥杯省赛——重合次数

目录 题目链接&#xff1a;1.重合次数 - 蓝桥云课 (lanqiao.cn) 题目描述 答案提交 运行限制 思路 总结 题目链接&#xff1a;1.重合次数 - 蓝桥云课 (lanqiao.cn) 题目描述 在同一天中, 从上午 6 点 13 分 22 秒到下午 14 点 36 分 20 秒, 钟表上的 分针和秒针一共重合…

HTML - 请你谈一谈img标签图片和background背景图片的区别

难度级别:中级及以上 提问概率:65% 面试官当然不会问如何使用img标签或者background来加载一张图片,这些知识点都很基础,相信只要从事前端开发一小段时间以后,就可以轻松搞定加载图片的问题。但很多人习惯用img标签,很多人习惯用backgro…

Java 数据类型转换

String 转 char 数组 String str "abc"; char[] charArr str.toCharArray();char 数组转 String char[] charArr{a, b, c}; String str new String(charArr);char 字符转 String 使用 String.valueOf() 方法 char ch a; String str String.valueOf(ch);使…

element-ui的年份范围选择器,选择的年份需等于或小于当前年份,选择的年份范围必须在三年之内

写在前面 日期限制处理&#xff08;禁用&#xff09;&#xff0c;下面我以我这边的需求为例&#xff0c; 选择的年份需等于或小于当前年份 选择的年份范围必须在三年之内 1.限制起始日期小于截止日期 1&#xff09;根据用户选中的开始日期&#xff0c;置灰不可选的日期范围&…

【腾讯云 TDSQL-C Serverless 产品体验】饮水机式使用云数据库

云计算的发展从IaaS&#xff0c;PaaS&#xff0c;SaaS&#xff0c;到最新的BaaS&#xff0c;FasS&#xff0c;在这个趋势中serverless(去服务器化&#xff09; 计算资源发展Physical -> Virtualisation -> Cloud Compute -> Container -> Serverless。 一、背景介绍…

什么是电子邮件组,为什么要使用它们?

在当今时代&#xff0c;电子邮件无处不在&#xff0c;尤其是对于商业活动而言。电子邮件的重要性不容忽视&#xff0c;因为它在沟通中极为高效。然而&#xff0c;电子邮件也存在降低工作效率和阻碍流程的风险。在这种情况下&#xff0c;电子邮件群组就是最佳的解决方案。什么是…

代码随想录算法训练营第二十九天|491.递增子序列,46.全排列,47.全排列 II

题目&#xff1a;491.递增子序列 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中至少有两个元素。你可以按任意顺序返回答案。 数组中可能含有重复元素&#xff0c;如出现两个整数相等&#xff0c;也可以视作递增序列的一…

【学习 在服务器上使用bypy直接下载百度云盘的资源。

参考&#xff1a;bypy 具体步骤 step1&#xff1a; pip install bypystep2&#xff1a; bypy info第一次输入该命令&#xff0c; 点击进入网址&#xff0c;点击登陆后&#xff0c;获取token&#xff08;10分钟内有效&#xff09;&#xff0c;然后输入到命令行&#xff1a;…

【linux深入剖析】深入理解基础外设--磁盘

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 前言1.磁盘物理结构2.磁盘…

Go 实战|使用 Wails 构建轻量级的桌面应用:仿微信登录界面 Demo

概述 本文探讨 Wails 框架的使用&#xff0c;从搭建环境到开发&#xff0c;再到最终的构建打包&#xff0c;本项目源码 GitHub 地址&#xff1a;https://github.com/mazeyqian/go-run-wechat-demo 前言 Wails 是一个跨平台桌面应用开发框架&#xff0c;他允许开发者利用 Go …

联想电脑VMware虚拟机VT开启虚拟化

以联想电脑为例。 关机重启&#xff0c; 有的电脑是按F2&#xff0c; 有的是按fnF2 进入BIOS&#xff0c;左右键&#xff0c;选择Configuration&#xff0c; 再上下键选择 Intel Virtual Technology 按回车键&#xff0c;再按上下键选择 Enable &#xff0c;回车确认。 按fn…

Xlinx相关原语讲解导航页面

原语就是对FPGA底层器件的直接调用&#xff0c;与IP功能是类似的&#xff0c;将原语的参数变成IP配置时的GUI界面参数&#xff0c;可能会更加直观。IP的缺陷在于繁杂&#xff0c;比如SelectIO IP内部包含IDDR、ODDR等等IO转换的功能&#xff0c;如果只想使用单沿转双沿一个功能…

ChatGPT全方位解析:如何培养 AI 智能对话技能?

简介 ChatGPT 的主要优点之一是它能够理解和响应自然语言输入。在日常生活中&#xff0c;沟通本来就是很重要的一门课程&#xff0c;沟通的过程中表达的越清晰&#xff0c;给到的信息越多&#xff0c;那么沟通就越顺畅。 和 ChatGPT 沟通也是同样的道理&#xff0c;如果想要C…

[软件使用-Vcftools / Plink ] VCF文件中剔除/提取一个或多个样本,两组方法实现及运算时间比较

官网&#xff1a;VCFtools 参数查看&#xff1a; 提取样本 --keep 剔除样本 --remove # 提取某几个样本 system("vcftools --vcf eg.vcf --keep keep.list --recode --out keep") # keep.list 是由每一行为一个样本ID组成的文件 tips: 运行比较耗时 官网&#xff…

【算法】二分算法题

个人主页 &#xff1a; zxctscl 如有转载请先通知 题目 1. 704. 二分查找1.1 分析1.2 代码 2. 34. 在排序数组中查找元素的第一个和最后一个位置2.1 分析2.2 代码 3. 35. 搜索插入位置3.1 分析3.2 代码 4. 852. 山脉数组的峰顶索引4.1 分析4.2 代码 5. 153. 寻找旋转排序数组中…

蓝桥杯简单STL

目录 vector vector定义 vector访问 常用函数 size() ​编辑 push_back(num) pop_back() clear 迭代器&#xff08;iterator) 迭代器定义 遍历数组示例 insert(it, element) erase(it) 标准模板库--STL&#xff0c;它包含了多种预定义的容器、算法和迭代器&…