Spring Bean 生成流程详细解析

很多人把spring的相关内容当作背八股文,认为只在面试时能用上,实际开发根本用不到。实际上早期的我也是这么想的,但随着开发年限的增长,解决了越来越多的难题后,不得不承认,这些基础知识的学习有着无法替代的作用。

就拿我实际遇到的一个例子来说:

有一个大型项目因为安全漏洞的原因要进行升级,需要从springboot1.0升级至springboot2.0,但发现springboot2的默认动态代理方式为CGLIB,而项目上很多地方利用的jdk代理对接口做了增强,切换至CGLIB导致了大量问题。根据百度的内容,设置了proxy-target-class=“false”,然而不起作用,最后发现是某一个三方包内设置了proxy-target-class=“true”,而这个属性只要在工程里任何地方设置过一次true,都会导致代理管理器的同名属性为true,最终采用CGLIB代理,那么有什么简单方式可以解决这个问题

先卖个关子,还是让我们一起学学Bean的生成吧

1引言
作为javaboy的必修课,spring一路伴随着开发者;同样的,也一路伴随着开发者面试,重要性不言而喻,我们经常遇见的问题比如:

代理对象是何时生成的?

循环依赖是怎么解决的?

能说说对Springr容器三级缓存的理解吗?

以上问题,都离不开对bean生成流程的熟悉与理解。但是不得不谈,目前网上文章鱼龙混杂,一些偏颇错误的分析四处流传,我们后面会提到一些常见谬传。至于现在,现在先和我们一起,深入的看下springBean的生成逻辑吧

2创建Bean的极简流程
我们开门见山,直接以单例对象为例子,说一个Bean的极简流程以及其目的

获取Bean定义
扫描工程内所有被标记的Bean,获取其类型,名称,属性,构造方法等信息,存在一个Map里

生成实例
这一步也很简单,遍历上述Map,利用Bean定义里的无参构造方法创建对象,和new 对象同理

属性装填
刚创建的对象所有属性都是默认值,需要我们给它装填上需要的内容

初始化
如果这个Bean实现了InitializingBean接口,则会调用你写在afterPropertiesSet方法里的内容。

到此,一个Bean就创建完毕了,是不是很简单?是的,很简单,逻辑也很清晰。

当然,上面四步是核心功能,Spring为了增强对这些Bean的修改能力,在2-生成实例 3-属性装填 4-初始化的前后都预留了处理点,Spring自己或用户都可以通过编写==Bean后置处理器(BeanPostProcessor)==来实现自己的目的,这些处理器会在对应的处理点被执行,从而完成对Bean的修改,下面会详细讲一下

3后置处理器(PostProcessor)
Spring中的后置处理器分为两大类:

一类是针对Bean工厂的BeanFactoryPostProcessor
一类是针对Bean的BeanPostProcessor
以上两者都是接口,Spring已经给定了一些实现类,用户也可以自己写一些实现类来实现全局的Bean相关的操作;顾名思义,BeanFactoryPostProcessor针对Bean工厂(它还有个子接口BeanDefinitionRegistryPostProcessor),调整Bean工厂的属性、影响Bean定义,注意此时还没有Bean进行实例化。BeanPostProcessor则更直接的作用于Bean实例生成过程中的修改。

BeanFactoryPostProcessor
很多人不知道在实际项目中这个处理器有什么用,好像我们不需要对Bean工厂或者Bean做什么改动吧?大部分项目确实不需要,但很多时候,我们需要添加一些自定义的Bean,或者出于项目需要,改动一些Spring原生Bean属性时就用的上了。

比如我们常用的myBatis组件,我们会在mapper层的接口上写@Mapper注解,最后就会在Spring中生成对应的Bean对象,然而这里有一个问题:

@Mapper注解不是Spring规定的Bean注解,怎么被扫描进容器的?

自然是依托于BeanFactory后置处理器。mybatis中写有工厂后置处理器的实现

图片
看名字也知道,这个处理器起了扫描的作用,找到了被我们标记的接口,并“捏造”一个Bean定义,并把Bean的类型设置为MapperFactoryBean.class,即工厂类,然后把它添加到Bean定义注册器中。

而在我们需要实例化这个Bean的时候,mybatis又会从这个工厂对象中使用getObject()为我们取出一个Bean实例,这个Bean实例是使用我们写的Mapper接口产生的代理,而后再把这个代理放入Spring容器

图片
BeanPostProcessor
而Bean后置处理器则更加常见,种类也更丰富,他们的详细作用和工作时机都可以在下图中看到

图片
契机问题的解决
让我们回到契机里提到的那个问题,这个问题简化的讲,其实就是有这么一个Spring内部的Bean名字为org.springframework.aop.config.internalAutoProxyCreator,它有一个属性proxy-target-class,这个属性决定了Spring动态代理的生成用的jdk动态代理还是CGlib,然而在很多地方(三方包)已经给他赋值。

我们必须在它被其他三方包赋值后 ,把它的属性值改为false。这个问题最终怎么做到的呢?就是利用了后置处理器,此处使用工厂后置处理器找到该Bean定义,修改其Bean属性

图片
4引用与缓存
从上面看,似乎创建一个Bean只需要四步(忽略后置处理器的步骤),十分简单。确实,如果我们的项目只需生成一个Bean,那只要按序完成这四步就可以了。

但实际上,Spring本身和我们的项目要生成的Bean数量远不止一个,复杂的项目一般会达到上千个Bean,Bean之间还有复杂的引用关系。我们不仅要存储这些Bean,还必须考虑到这些引用情形,从而引入缓存的机制。

引用已有的Bean
图片
如图,上述是一种最简单的引用,Parent 里面引用了 Child ,。理想的情况下,我们先创建了Child并保存起来,那么在创建Parent的时候,直接引用现成的Child就好(此处用@DependsOn保证这种顺序)。那么这时我们可以说,容器只需要使用一级缓存,就像养鸡场里饲养着许多鸡,这个缓存里存的就是各个现成的Bean,直接取用即可。

引用未创建的Bean
上述的Parent 里面引用了 Child案例,只是一种理想情况,实际上,大部分的Bean之间加载顺序并不会特意指定,创建的先后顺序自然没了保障(spring会执行默认的加载顺序,如字母排序)。

比如这个案例,如果先创建的是Parent,那么当我们做到属性装填这一步的时候,就会发现Parent的属性里,引用了一个未知的Bean —— Child。

图片
这个时候Spring就会去搜寻并创建Child,此时Parent的创建就停滞了。那么这个创建未半而中道崩殂的Parent也需要有一个地方存起来啊。你或许会说,还是存在上面的一级缓存里面不行吗?

当然可以!但本着人以类聚物以群分的观念,对于这些创建了一半就中断的Bean,我们还是专门引入了三级缓存供其栖息。我们知道,此时Parent已经实例化了,但属性装填没完成,像个未孵化的蛋,而三级缓存就是个保温箱,是存放这些“蛋”的地方。实际上三级缓存里存的全是Bean工厂,可以通过Bean工厂的getEarlyBeanReference获取到这个未完成的Bean(蛋)。

循环引用(循环依赖)
如果不仅Parent里面引用了Child,Child里面也引用了Parent,那么显然,这就构成了循环引用。

图片
我们假定Spring先加载了Parent,后发现需要注入Child,又去加载Child,过程中又发现需要注入Parent,那么又去加载Parent…… 那Spring会这么无限的加载下去吗?

答案我们都知道,自然是不会的。实际上,每开始加载一个Bean,Spring都会把Bean名称记录在一个叫SingletonCurrentlyInCreation的Set集合里。

顾名思义,这个集合里都是正在创建中的Bean,这个集合在其他的文档中很少提及,但显然他的作用十分巨大。因为第二次加载Parent时,Spring就发现Parent已经在这个集合中了,才意识到进入循环引用了。

图片
当发现进入循环引用后,自然Spring不会再傻乎乎的走再走一遍Parent的加载逻辑,而是从三级缓存中取出未完成的Bean,做一些处理后,然后将其放入二级缓存。

这一过程相当于从保温箱取出来未孵化的鸡蛋,孵化出小鸡后,放到专门的小鸡培养室中。而此时,只需要返回这只小鸡(Parent)就可以了,你或许会说,我要的是成品鸡,你给我小鸡有什么用,功能什么的能有保障吗?别急,我下面就为你解释这样的可行性。

循环引用中的代理
我们都知道Parent是创建了一半被放入缓存中的,此时它已经完成的步骤是生成实例正在卡着的步骤是属性装填和初始化,被从缓存中取出后,这两个步骤仍然是未完成的,但我们无需担心,因为此刻我们仅需完成引用,即我要引用Parent(成鸡),你现在给我返回半成品(小鸡)也没关系,因为我现在也不是要立刻就用你,只要你保证小鸡 成鸡在内存中的地址一样即可,即小鸡和成鸡是同一个对象。

你或许会问,小鸡长着长着,还能变了人不成?怎么可能小鸡和成鸡就不是同一个对象了呢?这就不得不谈代理模式了

图片
我们这里不去细谈代理流程,你只需要知道代理模式会产生一个新的对象,相当于一个霸道中介,原本你可以直接联系小鸡,现在小鸡的联系被中介切断了,你需要找小鸡就只能联系中介。所以,一旦成鸡后续需要代理,我们需要联系的就是成鸡的代理了,此时你给我小鸡的联系方式不顶用。

为避免这种情况,我们只能给小鸡生成中介。是的,原来中介是只给成鸡用的,但现在不得不提前到小鸡阶段了,生成中介后,返回给我们小鸡的-中介的-联系方式(即半成品Bean的-代理的-引用),事实上如果你看源码,对成品和半成品Bean生成代理用的是同一个方法wrapIfNecessary,因此生成代理的效果是一样的。当然你也许仍然有顾虑,对成品和半成品生成代理真的没差别吗?

的确,这里就不得不提Spring的代理的特殊点了,代理的基础就是大名鼎鼎的AOP 或者说 切面增强,然而Spring的增强仅针对方法。而半成品和成品,最大的差异是属性值,方法却是一样的,因此增强的效果肯定是一样的。如果哪天Spring的代理生成时会用到当前属性值,那不同阶段的代理功能才会有差异。

5三级缓存的解读
关于三级缓存,市面上有太多的解读文章,也是面试时经常问到的点,我们不妨解读一下三级缓存。

图片
我们平常说的三级缓存,大多数人会想到CPU的三级缓存,硬件上之所以缓存分级,是对于成本与性能的考量,一级缓存最快,所以CPU优先从一级缓存取东西,但同样一级缓存最贵,存不了太多数据,所以需要二级缓存。

而这里,三级缓存并没有性能上的区别,所以划分三级缓存并非必须。实际上一个Bean,在同一时间只会出现在某一级缓存中,因此我们可以直接产出一个暴论:Spring可以不用所谓三级缓存,甚至说只需要一个集合就能存下全部

但为什么这里要这么做,因为这是逻辑分层而非必要分层,三级缓存存着不同状态的Bean罢了:一级缓存存成品鸡,二级缓存存小鸡,三级缓存存鸡蛋 一级比一级原始,你要非把成品鸡、小鸡、鸡蛋搁一个房子里也不是不行,所以这种分层是基于逻辑清晰而非逻辑必需。

这里还有个误区,很多人说是因为代理的存在,导致需要三级缓存,如果没有代理,两级就够了。实际上三级缓存并不是因为代理导致的,不管有没有代理,都是三级缓存。

就像我说的一级缓存存成品鸡,二级缓存存小鸡,三级缓存存鸡蛋 ,这里面并不区分代理,成品鸡或者成品鸡的代理都在一级缓存;小鸡或者小鸡的代理都在二级缓存。

实际上我们看代码,只要发生了循环引用,都会导致Bean从三级缓存取出,并放入二级缓存。这个过程中执行wrapIfNecessary,不管生不生成代理都是一样的,只不过如果需要代理,放入二级缓存的是小鸡的代理;如果不需要代理,放入二级缓存的就是小鸡本鸡,因此我们可以说 不管有没有代理,三级缓存的模式都没有变化。

6创建Bean的极详细流程
多说无益,我根据Spring4的源码整理了一份详细的生成流程,这图说是全网最细也不为过,欢迎大家补充和指正

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

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

相关文章

【剑指offer--C/C++】JZ6 从尾到头打印链表

一、题目 二、本人思路及代码 直接在链表里进行翻转不太方便操作,但是数组就可以通过下标进行操作,于是, 思路1、 先遍历链表,以此存到vector中,然后再从后往前遍历这vector,存入到一个新的vector,就完成…

【C++从0到王者】第五十一站:B+树

文章目录 一、B树1.B树的概念2.B树的特性3.B树的插入的过程4.总结 二、B*树1. B*树的概念2.B*树的分裂 三、总结四、B树系列和哈希和平衡搜索树作对比五、B树的一些应用1.索引2.MySQL索引3.MyISAM2.InnoDB 一、B树 1.B树的概念 B树是B树的变形,是在B树基础上优化的…

Java网络爬虫实践:解析微信公众号页面的技巧

在当今数字化时代,信息获取已经成为了一项至关重要的任务。然而,随着信息量的爆炸性增长,人工处理这些信息已经变得不太现实。这时候,网络爬虫就成为了一种强大的工具,能够帮助我们从海量的网页中快速准确地获取所需信…

【软件测试】上岗第一天,组长就要我做自动化测试?我该咋办?

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 如果你恰好刚刚进…

如何在Linux系统Docker部署Dashy并远程访问内网服务界面

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Cesium-记录差值线

/*** param {Object} startTime Date格式的开始时间* param {Object} endTime Date格式的结束时间* param {Object} coordinates [x1,y1,x2,y2,x3,y3.......]* param {Object} entityCollection 实体收集器*/ async function interpolationLine(startTime,endTime,coordinat…

git log查看项目中各成员的代码提交情况,行数,次数等

前言: 项目中通过git来提交代码后,可以通过git log的不同命令来查看项目中各成员的代码提交情况,行数,次数。 具体操作: 1、查看所有的提交日志,回车查看更多,不过太多会卡死,需要重新开 git …

神经网络(neural network)

在这一章中我们将进入深度学习算法,学习一些神经网络相关的知识,这些是有更加强大的作用,更加广泛的用途。 神经元和大脑(neurons and the brain): 我们对于我们的编程的进步主要来自我们对于大脑的研究,根据我们对于大脑的研究…

【中间件】docker的安装

📝个人主页:五敷有你 🔥系列专栏:中间件 ⛺️稳中求进,晒太阳 .卸载旧版 首先如果系统中已经存在旧的Docker,则先卸载: yum remove docker \docker-client \docker-client-latest \doc…

【C++】string类的基础操作

💗个人主页💗 ⭐个人专栏——C学习⭐ 💫点击关注🤩一起学习C语言💯💫 目录 导读 1. 基本概述 2. string类对象的常见构造 3. string类对象的容量操作 4. string类对象的访问及遍历操作 5. 迭代器 6.…

[CVE-2024-27198]TeamCity<2023.11.4 身份验证绕过漏洞

一、软件背景 TeamCity是一款由JetBrains公司开发的持续集成和持续部署工具。它提供了一个集成的平台,用于自动构建、测试和部署软件项目。TeamCity支持各种不同的编程语言和版本控制系统,包括但不限于Java、C#、Python、Git、SVN等。 二、影响版本 t…

javaWebssh教师荣誉库管理系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 java ssh在线授课辅导系统是一套完善的web设计系统(系统采用ssh框架进行设计开发),对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为TOMCAT7.0…

【DP】蓝桥杯第十三届-费用报销

#include<iostream> #include<algorithm> #include<cstring> #include<set> #include<queue> using namespace std; const int N1010; int dp[N][5010];//dp[i][j]:选到第i个物品是否能取到价值j&#xff1b; int month[13]{0,31,28,31,30,31,30…

labuladong刷题笔记五(递归)

你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系统会自动报警。 给定一个代表每个房屋存放金额的非…

代码随想录day15(2)栈与队列:滑动窗口最大值(leetcode239)

题目要求&#xff1a;给定一个数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。 思路&#xff1a;首先的想法就是暴力方法&#xff0c;遍历一遍…

网络口碑如何扭转舆论导向?

俗话说得好&#xff0c;好事不出门&#xff0c;坏事传千里&#xff0c;当企业的负面消息出现在网络上时&#xff0c;企业必须追责源头&#xff0c;从根源开展舆情管控&#xff0c;如果是恶语中伤&#xff0c;则可以付诸于法律。但是&#xff0c;如果是一些无关紧要的负面评价&a…

专访|云安全攻防:从理论到应用的全面探索

2023年11月&#xff0c;美国核研究实验室&#xff08;INL&#xff09;遭遇数据泄露。同年10月&#xff0c;索尼的员工数据在MOVEit攻击事件中被泄露。2024年2月&#xff0c;某知名制造商因云存储服务器的配置错误导致了敏感数据泄露。 这些事件表示企业必须重视云安全建设&…

第一套试卷大题

1.邻接矩阵和邻接表的写法&#xff1a; **介绍&#xff1a;**该图是一个无向图&#xff0c;所以邻接矩阵一定是对称的&#xff0c;而邻接表某节点的边数为无向图某节点的连接数 无向图的邻接矩阵&#xff1a; 无向图的邻接表&#xff08;不唯一&#xff09;&#xff1a; 根据无…

快速排序hoare优化

目录 三数取中法选key 优化点 基本思想 代码实现 小区间优化 优化点 基本思想 代码实现 由于hoare版快排在一些特殊情况下性能并不优&#xff0c;这里我们进行一些优化。 三数取中法选key 优化点 当数据有序时&#xff0c;快排就会很吃力&#xff0c;这是为什么呢…

[C语言]——分支和循环(1)

目录 一.if语句 1.if 2.else 3.分支中包含多条语句 4.嵌套if 5.悬空else问题 二.关系操作符 三.条件操作符 C语⾔是结构化的程序设计语⾔&#xff0c;这⾥的结构指的是顺序结构、选择结构、循环结构&#xff0c;C语⾔是能够实现这三种结构的&#xff0c;其实我们如果仔细分析&a…