手写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,一经查实,立即删除!

相关文章

【Java】用吃拉面的方式打开Java程序的执行流程

你还在为记不住Java程序的执行流程而苦恼吗&#xff1f;那你来对地方了&#xff0c;这篇博客将让你一次记住终生不忘。 接下来&#xff0c;我们用去兰州拉面的过程&#xff0c;来形象地看一下&#xff0c;当你运行一个的main方法时&#xff0c;到底发生了什么&#xff1a; 走进…

c++找最高成绩

根据给定的程序&#xff0c;写成相关的成员函数&#xff0c;完成指定功能。 函数接口定义&#xff1a; 定义max函数&#xff0c;实现输出最高成绩对应的学号以及最高成绩值。 裁判测试程序样例&#xff1a; #include <iostream> using namespace std; class Student{…

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。 一、背景介绍…

LeetCode 264. 丑数 II

解题思路 三个指针进行遍历&#xff0c;归并排序。 相关代码 class Solution {public int nthUglyNumber(int n) {int temp1;int a[] new int[n];a[0] 1;for(int i0,j0,k0,index1;index<n;index){temp Math.min(a[i]*2,Math.min(a[k]*5,a[j]*3));a[index] temp;if(te…

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

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

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

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

VUE路由跳转、传参总结

路由跳转方式有哪些&#xff1f; 在Vue中&#xff0c;路由跳转主要有以下几种方式&#xff1a; 使用router-link组件&#xff1a; <router-link to"/home">Home</router-link>使用编程式导航 // 字符串 this.$router.push(home)// 对象 this.$router.…

大学生拥有一台服务器可以做什么?

云服务器的崭新世界&#xff0c;充满了无限的可能性和激动人心的挑战。无论你是想要建立个人网站、运行定时任务&#xff0c;还是学习Linux系统&#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.磁盘…

汽车差速器原理?

差速器&#xff08;Differential&#xff09;是汽车传动系统中的重要组成部分&#xff0c;主要作用是允许车辆驱动轮以不同速度旋转&#xff0c;从而使车辆能够顺利转弯并保持稳定性。其基本原理如下&#xff1a; 解决转弯问题&#xff1a; 当车辆转弯时&#xff0c;内侧轮和外…

pnputil卸载Sangfor

pnputil /delete-driver sangforvnic.inf /uninstall Microsoft PnP 工具PNPUTIL [/add-driver <...> | /delete-driver <...> |/export-driver <...> | /enum-drivers |/enum-devices [<...>] | /enum-interfaces [<...>] |/disable-device &l…