【Spring面试】四、Bean的生命周期、循环依赖、BeanDefinition

文章目录

  • Q1、Bean有哪些生命周期回调方法?有哪几种实现方式?
  • Q2、Spring在加载过程中Bean有哪几种形态
  • Q3、解释下Spring框架中Bean的生命周期
  • Q4、Spring是如何解决Bean的循环依赖的
  • Q5、Spring是如何帮我们在并发下避免获取不完整的Bean的?
  • Q6、描述下BeanDefinition的加载过程
  • Q7、如何在所有的BeanDefinition注册完成后做扩展?

Q1、Bean有哪些生命周期回调方法?有哪几种实现方式?

包括创建和销毁,实现方式分别有三种:

A1:使用@PostConstruct和@PreDestory

在这里插入图片描述

在相应的类中写初始化和销毁方法,加@PostConstruct和@PreDestory注解:

在这里插入图片描述

写个测试类:

在这里插入图片描述

A2:实现InitializingBean和DisposableBean接口

在这里插入图片描述

A3:使用init-method和destroy-method方法

在这里插入图片描述
注意这时候不适用于@Component,而是@Bean和xml:

在这里插入图片描述

最后,关于这三种方式的执行顺序,同时保留三种初始化和销毁的代码,可以看到:

在这里插入图片描述

Q2、Spring在加载过程中Bean有哪几种形态

答案:

在这里插入图片描述

  • 概念态Bean:刚使用@Bean或者<bean/>配置完各项Bean的信息
  • 定义态Bean:创建完ApplicationContext容器,概念态的信息如:id、scope、lazy等信息被读取到BeanDefinition对象中
  • 纯净态Bean:Spring容器通知BeanFactory生产,但这时只是刚实例化,没有依赖注入,先存于二级缓存里(循环依赖问题才会体现出纯净态的作用
  • 成熟态Bean:属性注入,成为一个成熟态Bean,加入到单例池(一个Map,也叫一级缓存)

Q3、解释下Spring框架中Bean的生命周期

答案:

Bean的生命周期,指的就是Bean从创建到销毁的整个过程,主要有四大步:

STEP1实例化

  • 通过反射去拿到构造函数来(new Instance)实例化
  • 当然实例化也可能是实例工厂、静态工厂等

STEP2属性赋值

  • 解析自动装配(DI的体现),可byName、byType、constractor
  • 这里当然还有循环依赖的情况

STEP3初始化

  • 调用那些XXXAware的回调方法
  • 调用初始化生命周期的回调方法(init-method)
  • 如果Bean涉及了AOP,还要创建动态代理

STEP4销毁

  • 在Spring容器关闭的时候调用
  • 调用销毁生命周期的回调方法(destroy-method)

Q4、Spring是如何解决Bean的循环依赖的

在这里插入图片描述
如上图,实例化完BeanA,要属性注入了,此时发现需要依赖Bean B,在IoC容器中找Bean B,没找到,开始创建Bean B,实例化完做DI发现需要Bean A,结果在IoC中没找到Bean A,于是就开始重复上面的这个流程,形成死循环。

答案:

Spring是采用三级缓存来解决循环依赖的,三级缓存分别是三个Map

  • 一级缓存是用来存储完整的Bean的
  • 二级缓存,存半成品的Bean,避免多重循环依赖的情况下,重复创建动态代理
  • 三级缓存,存lambda表达式

在这里插入图片描述

  • 首先创建Bean A,此时去一级缓存中getBean(A),发现没有,则进行实例化,并加入三级缓存
  • 接下来给A属性赋值,发现依赖Bean B
  • 因此去一级缓存中getSingleton(B),找不到B,则createBean(B)
  • 实例化Bean B后并加入三级缓存,此时给B属性赋值,发现依赖A,去getSingleton(A),此时先去一级缓存,找不到,二级缓存也没有找到
  • 最终到三级缓存,此时第二次调用getBean(A)了,就会调用三级缓存,并将结果保存到二级缓存

在这里插入图片描述

  • 此时B里的A就已经赋值完成了,虽然是个半成品的A(就好比算命的说这个人未来是你对象,人就是这个人,哪怕ta现在还未成年)

  • 到此,循环被打破,B被存储到一级缓存,addSingleton(B),并remove二级三级缓存

在这里插入图片描述

  • 将创建好的Bean B返回给A,Bean A也完成了属性赋值
  • A再进行初始化阶段,最终创建完成

关于三级缓存:

  • 三级缓存是函数接口,通过lambda把方法传进去(把Bean的实例和名字都传进去)(aop创建)
  • 不会立即调用
  • 会在ABA,即创建B时第二次getBean(A)才调用三级缓存(如果实现了aop,就会创建动态代理,如果没有,依然返回Bean的实例)
  • 结果会放入二级缓存,避免再有Bean C也依赖A时重复创建
ps:为什么不在实例化A后直接放进一级缓存的Map中去?
--------
- 因为此时A尚未创建完整,所有属性都是默认值,并不是一个完整的对象
- 如果直接扔进一级缓存,在执行业务时可能会抛出未知的异常

相关问题:

Q4.1、二级缓存能不能解决循环依赖?
  • 如果只是想跳出这个死循环问题,那一级缓存就可以解决(实例化后直接扔一级缓存),但这样无法避免并发下获取不到完整的Bean
  • 二级缓存也可以解决循环依赖,只不过如果出现重复的循环依赖,即ABA、ACA,就会多次创建AOP的动态代理
Q4.2Spring有没有解决多例Bean的循环依赖?
  • 多例Bean,不同的Bean,创建完也不会存到一级缓存、二级缓存、三级缓存中,不会使用缓存进行存储,因为每次使用都会重新创建
  • 不缓存早期形态的对象,就无法解决循环问题(需要一个缓存保存早期形态的对象,来做为死循环的出口打破循环)
Q4.3Spring有没有解决构造函数参数Bean的循环依赖?
  • 构造函数里的循环依赖也会报错
  • 可以通过@Lazy注解解决,使用@Lazy就不会立即创建所依赖的Bean了,而是等到用到,才通过动态代理进行创建
    在这里插入图片描述

Q5、Spring是如何帮我们在并发下避免获取不完整的Bean的?

问题分析:

不完整的Bean,即只是完成了实例化,没有完成属性赋值(DI)和初始化(生命周期回调)。其次,并发下,假如有两个线程,都来getBean:

在这里插入图片描述
线程1以微小优势先来创建bean,实例化后放进三级缓存,此时,没有属性赋值和初始化,线程2进来getBean,getSinfleton去三个缓存中找,就拿到了不完整的Bean。

答案:

双重检查锁,2个同步锁,2次检查一级缓存

在这里插入图片描述

  • 线程1进来,先在缓存中获取,没获取到,先给缓存加锁(一级缓存未加锁)
  • 接下来进行实例化、属性赋值等操作,并放入三级缓存,这些过程也加锁
  • 此时线程2进来,getBean(A) -> doGetBean(A) -> getSingleton(A,boolean)
  • 先去一级缓存获取,一级只存完整的Bean,自然获取不到
  • 此时想去二级、三级缓存获取,但二三级被加锁了,线程2阻塞
  • 直到线程1走完流程,得到一个完整的Bean,并放到一次缓存,remove二三级缓存,返回一个null
  • 线程1释放锁
  • 线程2去二三级缓存获取到null,此时并不是新创建,二是再调用getSingleton,第二次去一级缓存(即双重检查)
  • 这次自然获取到了一个完整的Bean

连问:为什么不给一级缓存加锁,一次缓存要是加锁,则直接阻塞在一级缓存等待结果,也不用二次检查了。

答案:

因为性能,加入线程2除了获取Bean A还获取Bean C,而Bean C已经创建好了,存在于一级缓存,如此就会导致已经创建好的Bean阻塞等待。

Q6、描述下BeanDefinition的加载过程

BeanDefinition:用来存放(定义)Bean的生产信息,决定Bean如何进行生产(定义态的Bean)

答案:

  • 创建Spring容器
  • BeanDefinitionReader读取配置
  • 配置类的解析器开始解析:@Bean、@Import、@Component…
  • 如果解析ComponentScan,则先找到包路径下的所有.class文件,判断类是不是标准的@Component(排除接口,抽象类)
  • 解析完后,注册BeanDefintion
  • BeanDefinition加入到BeanDefinitionMap,交给后面的BeanFactory来生产

在这里插入图片描述

Q7、如何在所有的BeanDefinition注册完成后做扩展?

回顾下之前的这张图:

在这里插入图片描述
重点就在BeanDefinition后置处理器这里:

在这里插入图片描述

创建Spring上下文对象(IoC容器时),源码中调用refresh方法,这个方法内部就调用了invokeBeanFactroyPostProcessors,去注册所有的BeanDefinition:

在这里插入图片描述
而接下来就会调用图中的BeanFactoryPostProcessor,就是修改BeanDefinition的时机,所以我们只需实现这个BeanFactoryPostProcessor接口即可,Spring就会去调用:

答案:

实现Bean工厂后置处理器接口BeanFactoryPostProcessor,即可在所有的BeanDefinition注册完成后做扩展

在这里插入图片描述

写个测试程序:

在这里插入图片描述

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

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

相关文章

基于springboot+vue的大学生智能消费记账系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

【电源专题】案例:异常样机为什么只在40%以下电量时与其他样机显示电量差异10%,40%以上电量差异却都在5%以内。

本案例发生在一个量产产品的测试中,因为产品带电池,所以需要测试产品对于电池电量显示的精确程度。产品使用的是最简单的开路电压查表法进行设计。 案例测试报告的问题在于不同样机之间电量百分比存在差异,大部分是在3%~4%之间。但在7.2V电压时,能够差异10%左右。 在文章:…

ESIM实战文本匹配

引言 今天我们来实现ESIM文本匹配&#xff0c;这是一个典型的交互型文本匹配方式&#xff0c;也是近期第一个测试集准确率超过80%的模型。 我们来看下是如何实现的。 模型架构 我们主要实现左边的ESIM网络。 从下往上看&#xff0c;分别是 输入编码层(Input Ecoding) 对前…

【Android知识笔记】进程通信(二)

一、Binder对象是如何跨进程传递的 binder传递有哪些方式?binder在传递过程中是怎么存储的?binder对象序列化和反序列化过程?binder对象传递过程中驱动层做了什么?总结 Binder 对象的跨进程传递主要靠 Parcel 的两个关键方法 writeStrongBinder() 和

nvm 一个nodejs版本管理工具

nvm 一个nodejs版本管理工具 NVM是什么 nvm全英文也叫node.js version management&#xff0c;是一个nodejs的版本管理工具。nvm和n都是node.js版本管理工具&#xff0c;为了解决node.js各种版本存在不兼容现象可以通过它可以安装和切换不同版本的node.js NVM下载 可在点此在…

vueshowpdf 移动端pdf文件预览

1、安装 npm install vueshowpdf -S2、参数 属性说明类型默认值v-model是否显示pdf--pdfurlpdf的文件地址String- scale 默认放大倍数 Number1.2 minscale 最小放大倍数 Number0.8 maxscale 最大放大倍数 Number2 3、事件 名称说明回调参数closepdf pdf关闭事件-pdferr文…

jmeter接口测试及详细步骤以及项目实战教程

在接口测试项目实战中&#xff0c;JMeter是一款非常强大和流行的自动化测试工具&#xff0c;它可以测试各种类型的应用程序&#xff0c;并通过采样和报告来识别性能瓶颈和API的问题。本文将为你提供一个基于实际项目的JMeter接口测试项目实战教程&#xff0c;指导你如何使用JMe…

潍坊科技学院图书馆藏《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书

潍坊科技学院图书馆藏《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书

金融行业的软件测试分析

随着金融行业的业务不断增加&#xff0c;金融交易模式的不断变化&#xff0c;金融机构对信息化的要求也越来越高&#xff0c;高质量的金融软件对于金融机构来说显得尤为重要。如何保证金融行业软件的质量&#xff0c;对金融行业软件的测试人员来说&#xff0c;也提出了更高的要…

Python进阶教学——多线程高级应用

目录 一、线程间的通讯机制 二、线程中的消息隔离机制 三、线程同步信号量 四、线程池和进程池 一、线程间的通讯机制 1、Queue消息队列 消息队列是在消息的传输过程中保存消息的容器&#xff0c;主要用于不同线程间任意类型数据的共享。消息队列最经典的用法就是消费者和…

Lua脚本

基本语法 注释 print(“script lua win”) – 单行注释 – [[ 多行注释 ]] – 标识符 类似于&#xff1a;java当中 变量、属性名、方法名。 以字母&#xff08;a-z,A-Z&#xff09;、下划线 开头&#xff0c;后面加上0个或多个 字母、下划线、数字。 不要用下划线大写字母…

变压器寿命预测(python代码,Logistic Regression模型预测效果一般,可以做对比实验)

1.数据来源官网&#xff1a;Data for: Root cause analysis improved with machine learning for failure analysis in power transformers - Mendeley Data 点Download All 10kb即可下载数据 2.下载下来后是这样 每一列的介绍&#xff1a; Hydrogen 氢气&#xff1b; Oxyge…

01目标检测-问题引入

目录 一、目标检测问题定义 二、目标检测过程中的常见的问题 三、目标检测VS图像分类区别 目标检测&#xff1a; 图像分类&#xff1a; 总结&#xff1a; 四、目标检测VS目标分割 目标分割&#xff1a; 目标检测是计算机视觉领域的一个重要任务&#xff0c;旨在从图像或…

DBeaver 下载、安装与数据库连接(MySQL)详细教程【超详细,保姆级教程!!!】

本文介绍DBeaver 下载、安装与数据库连接&#xff08;MySQL&#xff09;的详细教程 一、DBeaver 下载 官网下载地址&#xff1a;https://dbeaver.io/download/ 二、安装 1、双击下载的安装包&#xff0c;选择中文 2、点击下一步 3、点击我接受 4、如下勾选&#xff0c;…

Linux中使用Docker安装ElasticSearch7.10.x集群

使用Docker安装ElasticSearch7.10.x单节点请访问这里 一、集群环境说明 服务器IP地址192.168.137.1&#xff0c;192.168.137.2&#xff0c;192.168.137.3 二、前期准备 1. 拉取镜像 docker pull elasticsearch:7.10.12. 首先需要创建一个用于生成秘钥的初始容器&#xff0…

面试官:我们深入聊聊Java虚拟机吧

哈喽&#xff01;大家好&#xff0c;我是奇哥&#xff0c;一位专门给面试官添堵的职业面试员 文章持续更新&#xff0c;可以微信搜索【小奇JAVA面试】第一时间阅读&#xff0c;回复【资料】更有我为大家准备的福利哟&#xff01; 文章目录 前言面试Java虚拟机内存模型垃圾收集器…

Mac版本破解Typora,解决Mac安装软件的“已损坏,无法打开。 您应该将它移到废纸篓”问题

一、修改配置文件 首先去官网选择mac版本下载安装 typora下载 然后打开typora包内容找到 /Applications/Typora.app/Contents/Resources/TypeMark/ 编辑器打开上面文件夹&#xff0c;这里我拉到vscode 找到page-dist/static/js/Licen..如下图 输入 hasActivated"…

Docker--未完结

一.Docker是干什么的 在没亲自使用过之前&#xff0c;再多的术语也仅仅是抽象&#xff0c;只有写的人或者使用过的人能看懂。 所以&#xff0c;作为新手来说&#xff0c;只要知道Docker是用于部署项目就够了&#xff0c;下面展示如何用Docker部署项目及Docker常用命令。 二、…

ajax day4

1、promise链式调用 /*** 目标&#xff1a;把回调函数嵌套代码&#xff0c;改成Promise链式调用结构* 需求&#xff1a;获取默认第一个省&#xff0c;第一个市&#xff0c;第一个地区并展示在下拉菜单中*/let pname axios({url: http://hmajax.itheima.net/api/province,}).t…

DataGrip 2023 年下载、安装教程、亲测可用

文章目录 前言1. 下载2. 安装3、DataGrip 常用操作4 推荐阅读 前言 DataGrip 是 JetBrains 发布的多引擎数据库环境&#xff0c;支持 MySQL 和 PostgreSQL&#xff0c;Microsoft SQL Server 和 Oracle&#xff0c;Sybase&#xff0c;DB2&#xff0c;SQLite&#xff0c;还有 Hy…