文章目录
- 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.2、Spring有没有解决多例Bean的循环依赖?
- 多例Bean,不同的Bean,创建完也不会存到一级缓存、二级缓存、三级缓存中,
不会使用缓存进行存储,因为每次使用都会重新创建
不缓存早期形态的对象,就无法解决循环问题
(需要一个缓存保存早期形态的对象,来做为死循环的出口打破循环)
Q4.3、Spring有没有解决构造函数参数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注册完成后做扩展
。
写个测试程序: