菜鸟的进阶--手写一个微型Spring

news2024/9/20 17:33:30

前言

想干嘛


  • 深入了解spring原理,特别是IOC容器是如何实现的?AOP是如何实现的?

  • 手写一个spring迷你版框架,实现容器和AOP机制。

我为什么想这么做


  • spring是整个java体系中最重要的框架,它整合第三方技术,将所有的技术可以融合在一起协同工作。

  • 想要真正了解spring最核心的两个概念:IOC & AOP

  • 想要通过手写一个简单的spring框架学习设计模式的思想。

完成度


总结一下本框架完成的功能:

IOC容器

  • beanFactory

  • 作为最重要的组件,充当着spring运行过程中核心中间人的身份。

  • 主要为它完成了:

  • 通过各种姿势获取bean。

  • 存储各式各样的bean实体对象,bean的描述信息等等。

  • 本框架中,主要完成了DefaultListableBeanFactory

  • ApplicationContext

  • 此容器是与beanFactory同级别重要的组件。它最重要的方法是refresh()方法。此方法会真正启动spring,它会注册相当多的处理器来对bean做处理。

  • 在此容器上,应用了模板方法的设计模式。通过分离出bean的生命周期各个阶段,通过注册bean处理器的方式来为它们做相关操作。

  • 本框架中,完成了AbstractApplicationContextAnnotationConfigApplicationContext 前者是一个父类,主要将bean的生命周期各阶段抽象出来,交由具体的实现类去做。例如像AnnotationConfigApplicationContext就是专门处理通过注解完成的bean注册,依赖注入等操作。类似的还有通过xml配置文件来处理配置文件内的bean,不过本框架中仅完成了AnnotationConfigApplicationContext。

  • BeanPostProcessor/ BeanFactoryPostProcessor

  • 后处理器分为了bean和beanFactory,前者是对单个bean做操作;后者是对整个工厂内的Bean做操作。

  • 后处理器是spring中真正"干活"的组件,它完成的主要工作有: 扫描包下的bean(工厂后处理器), 对bean的生命周期各阶段进行操作(bean后处理器)

  • 完成的处理器主要有:

  • 工厂后处理器:

  • ConfigurationClassPostProcessor

  • 主要完成对类文件的扫描,对相关注解的扫描,例如@Configuration,@Component等。

  • bean后处理器:

  • AutowiredAnnotationBeanPostProcessor

  • 根据名字也猜得出来,此处理器主要处理@Autowired注解的依赖注入,以及对于@Value的解析以及注入。

  • CommonAnnotationBeanPostProcessor

  • 此处理器主要处理@Resource注解的依赖注入,@PostConstruct 注解的初始化方法,@PreDestroy注解的销毁前方法。

  • LazyInjectBeanPostProcessor

  • 此处理器主要解决单例bean对多例bean的依赖注入时懒加载的问题。

  • AspectBeansInitBeanPostProcessor

  • 此处理器主要解决当依赖注入的对象是代理对象时,对其从sourceBean替换为代理bean的问题。

  • AnnotationAwareAspectJAutoProxyCreator

  • 此处理器主要处理AOP机制的相关操作。例如生成代理对象。

AOP机制

  • 本框架的AOP中,主要完成了通过注解方式完成对于切面类的织入。

  • 目前已完成的注解有: @Aspect ,@Pointcut,@Aroud,@Before,@After

  • 切点表达式的解析目前完成了三种: execution,@annotation,within

  • 通过AnnotationAwareAspectJAutoProxyCreator这个处理器来完成所有代理对象的生成。注意,此时虽然生成了代理对象,但是我并没有在这个处理器中完成对其的替换,因为仅仅是解析切点表达式,解析各个注解就已经让这个类特别的"胖"了。并且也是考虑程序设计时的单一职责原则。

  • 生成代理对象的方式采用了cglib。原先也是计划使用jdk动态代理的,但是后面遇到了一个无法解决的问题: 那就是对于依赖注入的对象是代理对象时,无法通过filed.set()进行反射注入,因为jdk动态代理生成的对象类型为Proxy$... 我去测试spring是怎么处理的适合,发现它也没有办法。。当我强制使用jdk动态代理,并且对被依赖注入的对象做增强时,它报了这样的错:


Description:

The bean 'testBean1' could not be injected because it is a JDK dynamic proxy

  • 最后我干脆就不用jdk的动态代理了,而是全部采用了cglib。

  • 通过上一步生成了代理对象之后,就可以通过AspectBeansInitBeanPostProcessor这个bean处理器完成对代理对象的替换啦。

遇到的问题


  • 纠结对于应该先实例化再进入生命周期的处理, 还是不实例化直接进入生命周期的处理

  • 如果先实例化,那么对bean生命周期的控制性就比较差。

  • 如果不预先实例化,那么依赖注入的顺序问题难以解决。

  • 最终还是选择了预先实例化,工厂后处理器负责注册@Component等注解的Bean,实例化的动作交给了context容器。

  • 通过autowired 后处理器完成对${}的解析,并且将值注入进对应的注解上面的value里面。但是,通过ImportAwareBeanPostProcessor 完成注入的时候,发现取得的method不是同一个(虽然class对象是同一个)。导致前面做的注解注入value,到ImportAwareBeanPostProcessor 的时候仍然是 ${xxx} ,后面通过将待处理的method加入一个集合结局这个问题。

  • 单例bean注入多例bean的时候,出现注入为null的情况。

  • 解决:

  • 参考spring的处理手段,增加一个@Lazy注解,专门处理注入多例bean.

  • 增加一个bean后处理器里专门处理这一情况。

  • 解析切点表达式,获取目标方法。由于目标方法可能存在多个,并且还有可能存在于不同的类中。所以需要考虑一个合适的数据结构来进行解析后的存储。这里我使用map,以class为Key methods为value.封装于poinCutDefinition中。

  • 如何处理pointCut注解,before..注解。如果它们的value不是目标方法,那么该去pointcut里面找。

  • 需要对这些注解信息做排序,将包含真正全类名的排在前面。

  • 得到了目标方法,就可以考虑增强的操作了。需要将增强方法织入到目标方法去。

  • 织入之前需要判断目标bean是否实现了接口,如果实现了接口统一使用jdk代理。否则使用cglib代理。

  • 还需要考虑before,after,around 它们对原方法的invoke顺序是有差别的。其中around的最不一样。

  • 这里需要考虑beanfactory里的bean有可能被替换的问题。(再次定义一个map用于存储aspect的bean)

  • 在这里获取到代理对象之后,并没有马上返回,而是留给了最后的bean后处理器。让它将aspectBean做最终的返回。

  • 需要考虑依赖注入之后的bean代理失效问题。主要是由于依赖注入拿到的bean和代理生成的bean不统一。

  • 先生成代理对象 还是先依赖注入?

  • 先生成代理对象,再依赖注入:

  • 代理对象生成之后,无法对该对象的原来的field成员变量赋值,(cglib代理对象没有拿到它的成员变量。)

  • 先注入,再生成代理对象:

  • 无法从已有对象生成,而是直接create一个新的。它的原来的成员变量需要通过get来获取。

  • 最终敲定: 先依赖注入,再生成代理对象。到最后的bean处理器的时候,专门处理将原先被依赖注入的bean替换成生成的代理对象。

  • 使用jdk动态代理生成对象,无法设置为依赖注入的bean,因为其类型为proxy类型。而被注入的Bean有可能是xxService类型。

  • 无法解决该问题。

  • 最终我自己测试了一下spring,发现spring用的也是cglib动态代理。

  • 报错如下:


APPLICATION FAILED TO START


Description:

The bean 'testBean1' could not be injected because it is a JDK dynamic proxy

The bean is of type 'com.sun.proxy.$Proxy51' and implements: com.markyao.springbootsource.bean.Tb1 org.springframework.aop.SpringProxy org.springframework.aop.framework.Advised org.springframework.core.DecoratingProxy

Expected a bean of type 'com.markyao.springbootsource.bean.TestBean1' which implements: com.markyao.springbootsource.bean.Tb1

Action:

Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.

  • 也就是说,依赖注入代理对象的情况下,只能使用cglib进行动态代理。

  • 最后

  • 不出意外的话,项目还存在很多bug,如果可以的话欢迎各位朋友玩一下,顺便帮我测试测试,如果有Bug可以及时通知我。

gitee

  • 目前已经将其上传至码云,感兴趣的朋友可以玩玩。

markyao-gitee

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

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

相关文章

交联剂134272-64-3,Maleimide-NH2 HCl,2-马来酰亚胺乙胺盐酸盐

【中文名称】N-(2-氨乙基)马来酰亚胺盐酸盐,2-马来酰亚胺乙胺盐酸盐【英文名称】 MAL-NH2 HCl,Maleimide-NH2 HCl,MAL NH2 HCl,Maleimide-amine HCl,MAL-amine HCl,N-(2-AMinoethyl)MaleiMide Hydrochlorid…

5年老测试员,面试被刷,别人说他不懂自动化测试.....

圈内认识的朋友最近跳槽了,之前在一家小公司干了5年测试,本来以为很容易跳一个高待遇的工作,结果却比想象的难,因为他不会自动化测试… 最近也看了很多人的简历,写的都是3年工作经验,但面试中&#xff0c…

对数据库几个范式的理解

数据库关系理论 这部分主要是几个概念很抽象,大家开始学可能学不明白。最近在准备复试,复习了一下相关的内容,顺便做一下总结。 先说几个名词: 候选码:能够唯一确定一个元组的属性集合称为候选码。注意是集合&#…

每日学术速递2.3

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.Cv、cs.LG 1.Compositional Prompt Tuning with Motion Cues for Open-vocabulary Video Relation Detection(ICLR 2023) 标题:通过基于错误的隐性神经表征的上下文修剪实现高…

Java基础学习笔记(十五)—— 集合(3)

集合1 HashMap 类1.1 HashMap 类概述1.2 HashMap 案例2 TreeMap 类2.1 TreeMap 类概述2.2 TreeMap 案例3 Properties集合3.1 Properties集合概述3.2 Properties基本使用3.3 Properties特有方法3.4 Properties和IO流相结合的方法4 可变参数与不可变集合4.1 可变参数4.2 不可变集…

2023.1.26

0、任务 今明两天任务,回答以下问题: 1、网络传输延迟有哪些?如何区分传输延迟和排队延迟? 2、如何理解路由器存储转发的过程? 3、拥塞是什么,为什么会发生拥塞,发生拥塞的表现是什么&#xff…

网络资源下载方式:http/https、ftp/sftp、BT种子、磁力下载、ed2k下载等的区别

文章目录参考资料序言中心化下载http/https下载ftp/sftp下载http与ftp下载方式的不同中心化下载的缺点中心化下载BT种子下载磁力下载ed2k下载推荐的下载器IDM下载器安装步骤IDM如何下载种子文件参考资料 一文读懂Bt种子、磁力链接、直链、p2p这些下载的区别 常说的BT下载、磁力…

【数据结构基础】图 - 基础和Overview

图(Graph)是由顶点和连接顶点的边构成的离散结构。在计算机科学中,图是最灵活的数据结构之一,很多问题都可以使用图模型进行建模求解。例如: 生态环境中不同物种的相互竞争、人与人之间的社交与关系网络、化学上用图区分结构不同但分子式相同的同分异构体…

情人节该送女友什么?分享四款适合送女生的数码好物

情人节快到了,对于有伴侣的人来说,这是一个浪漫的日子。在这个浪漫的日子,一些生活仪式感是必不可少的。最近看到不少人问,适合女生的数码好物有哪些?下面,我来给大家推荐几款适合送女生的数码好物&#xf…

动态规划DP与记忆化搜索DFS 题单刷题(c++实现+AC代码)

文章目录数字三角形滑雪挖地雷最大食物链计数采药疯狂的采药5倍经验值过河卒洛谷动态规划入门题单: 提单传送门 数字三角形 观察下面的数字金字塔。写一个程序来查找从最高点到底部任意处结束的路径,使路径经过数字的和最大。每一步可以走到左下方的点也…

“深度学习”学习日记。卷积神经网络--卷积层

2023.2.3 CNN中出现一些新的概念:填充、步幅 等,此外各层中传递的数据是有形状的,与之前的全连接层神经网络完全不同; 一、全连接层存在的问题: 全连接层神经网络使用了Affine层,在相邻的神经元全部连接…

php7.3.4 pdo方式连接sqlserver 设置方法

我这边用的php是7.3.4版本的,大家设置的时候看一下。一、首先要开启php的sqlsrv扩展1.下载SQLSRV58.EXE,我的php版本是7.3.4https://docs.microsoft.com/en-us/sql/connect/php/release-notes-php-sql-driver?viewsql-server-2017#previous-releases拷贝到浏览器打…

内网渗透(二)之基础知识-工作组介绍

系列文章 内网渗透(一)之基础知识-内网渗透介绍和概述 注:阅读本编文章前,请先阅读系列文章,以免造成看不懂的情况!! 工作组介绍 1、工作组的介绍 在一个大型单位里,可能有成百上千台计算机互相连接组成局域网,它…

Rancher 部署 MongoDB

文章目录前置部署创建 Headless开始部署测试前置 背景:在 K8S 集群用 bitnami 部署 MongoDB 有一定的学习成本,有兴趣可以参考 k8s 部署 mongodb 三种模式,且部署后发现 MongoDB 会随着时间推移占用越来越多的内存,暂没找到原有&…

计算机如何在本地硬盘安装WinPE系统

环境: 联想E14 Win 10专业版 U盘魔术师V6 30G硬盘分区 双硬盘:128G固+1T机 DiskGenius UltraISO 问题描述: 如何在本地硬盘安装WinPE系统 解决方案: 一、使用软件制作硬盘PE系统 1.机械磁盘先分区分一个30G分区 …

Java 中的Type类型及其实现【学习记录】

概述 在JDK1.5之前只有原始类型,此时所有的原始类型都通过字节码文件类Class进行抽象。Class类的一个具体对象就代表一个指定的原始类型。 JDK1.5加入了泛型类,扩充了数据类型,从只有原始类型基础上扩充了参数化类型、类型变量类型、通配符…

OpenStack使用Skyline Dashboard面板替换默认Horizon面板

书接上回 OpenStack Yoga安装使用kolla-ansible 忘记提示了。如果截止发稿今天,使用最新zed版本,在最后一步部署阶段会报错,好像是rabbitMQ重启失败。所以建议使用最新版再退一个版本 官方文档 skyline-apiserver/README-zh_CN.md at maste…

一文入门图像分类

文章目录一、卷积网络1.1 卷积的参数量1.2 卷积的计算量1.3 降低模型参数量和计算量的方法1.3.1 GoogLeNet 使用不同大小的卷积核1.3.2 ResNet 使用11卷积压缩通道数1.3.3 可分离卷积二、Transformer2.1 注意力机制 Attention Mechanism2.2 多头注意力 Multi-head (Self-)Atten…

基于SSM框架宠物管理系统

一、项目简介 本项目是一套基于ssm框架宠物管理系统,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含:项目源码、数据库脚本等,该项目附带全部源码可作为毕设使用。 项目都经过严格调试,eclipse…

学习笔记:Java 并发编程⑥_线程池

若文章内容或图片失效,请留言反馈。 部分素材来自网络,若不小心影响到您的利益,请联系博主删除。 视频链接:https://www.bilibili.com/video/av81461839配套资料:https://pan.baidu.com/s/1lSDty6-hzCWTXFYuqThRPw&am…