面试官:说说 Spring.getBean() 流程和循环依赖的解决!
共 1289字,需浏览 3分钟
·
2021-10-24 14:12
你知道的越多,不知道的就越多,业余的像一棵小草!
你来,我们一起精进!你不来,我和你的竞争对手一起精进!
编辑:业余草
cnblogs.com/insaneXs/p/12768884.html
推荐:https://www.xttblog.com/?p=5281
getBean流程介绍(以单例的Bean流程为准)
getBean(beanName)
从BeanFactory中获取Bean的实例对象,真正获取的逻辑由doGetBean
实现。
doGetBean(beanName, requiredType, args, typeCheckOnly)
获取Bean
实例的逻辑。
1 transformedBeanName(beanName)——处理Bean的名字
transformedBeanName()
对BeanName做一层转换,这层转换主要设计两方面的处理,一是对BeanFactory的Name做一些处理(通常FactoryBean
前面会增加一个&
符号用来和BeanName区分,例如假设一个FactoryBean的name为&myFactoryBean
,那么我们它创建的bean的name就为myFactoryBean
),另一个是针对别名的处理。
2 getSingleton(beanName, allowEarlyReferrence)——从三级缓存中搜索
getSingletion()
方法会先检查BeanFactory.singletionObjects
是否已经存在这个Bean的单例。singletionObjects
是BeanFactory
内部的一个Map,存放已经是初始化完成的bean对象,其中key存放的是beanName
,而value存放的是Bean实例。
如果singletionObjects
中不存在但是该Bean是否正在创建中,则会尝试从earlySingletionObjects
中获取。earlySingletionObjects
也是一个map,存放的是已经创建出对象,但是还未设置属性的bean。
如果在earlySingletonObjects
中依旧找不到Bean对象,那么要继续考虑通过ObjectFactory
创建Bean对象的情况。
❝这里就引出了Spring中关于解决循环依赖问题的三级缓存。关于循环依赖的处理我们最后再分析。
❞
/** Cache of singleton objects: bean name --> bean instance */
private final Map singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map earlySingletonObjects = new HashMap<>(16);
3 getParentBeanFactory().getBean()——从父容器中获取Bean
我们知道HierarchicalBeanFactory提供了BeanFactory父子容器的关系。子容器可以获取父容器中的Bean。
当我们从子容器中的三级缓存中获取不到Bean,且找不到该Bean的定义时,Spring会转而去父容器中查找,如果父容器中依旧没能找到,则说明该Bean不存在。如果父容器中存在,则从父容器中获得Bean。
4 markBeanAsCreated(beanName)
这步只是对这个BeanName做个标记,表示这个BeanName对应的Bean已经被创建或是正在被创建。
5 getMergedLocalBeanDefinition(beanName)
BeanDefinition是Spring中对Bean的定义和描述,其中难免会有很多属性和配置。在早起的xml时代,这些配置都需要人员编码,因此Spring对BeanDefition做了一种继承的机制,可以定义一个父定义,然后子类通过继承获得父定义中的属性(当然也支持覆盖父定义中的属性)。有点类似先定义一个宏。
而这个方法就是将父子BeanDefition整合一下,得到一个MergedBeanDefition。
6 getDependsOn()——处理DependsOn
Bean可以通过DependsOn
来显示控制Bean的初始化顺序(和依赖注入时的隐式初始化顺序略有不同,显示的DependsOn不用要求两个Bean之间存在引用关系)。
Spring会优先创建DependsOn指定的Bean,这一过程中,也可能存在"循环依赖"的问题,但是这种情况下的"循环依赖"问题Spring是无法内部自己解决的(因为通常使用DependsOn,就是想强制让否个Bean优先启动)。
7 getSingleton(beanName, ObjectFactory)——获取Bean实例
这是对getSingleton
方法的重载,前文版本中的getSingleton
方法只会从三级缓存中搜索Bean的实例,而这个重载方法会真正去创建Bean的实例。
在真正实例化对象前,依旧会先从singletonObjects
检查一次对象是否已经被创建,如果没有,方法就上锁,并进入实例化对象的流程。
7.1 beforeSingletonCreation(beanName)——创建前的处理
这步通常是做一些校验,并把该Bean标记成正在创建。
7.2 singletonFactory.getObject()——通过ObjectFactory创建对象
具体的对象创建过程会调用AbstractAutoWireCapableBeanFactory.createBean(beanName, beanDefinition, args)
方法。
7.2.1 resolvedClass()——解析bean类型
这步主要针对bean类型做解析。
7.2.2 preparedMethodOverrides()——overrideMethod的处理
这里的overrideMthod主要是针对bean中的look-up
和replace
等配置。有兴趣的读者可以自己展开了解。
7.2.3 resolveBeforeInstantiation()——在真正创建bean前,给用户一次控制bean创建的过程
用户可以通过InstantiationAwarePostProcessor.postProcessBeforeInstantiation(beanClass, beanName)
方法先行创建出Bean对象。InstantiationAwarePostProcessor
接口继承自BeanPostProcessor
,主要是针对Bean的实例化前后阶段设置了回调。
如果在这步中返回了Bean的实例,那么Spring不会再执行默认的创建策略。
这步留给用户的拓展还是比较有用的,用户可以根据该特性为某个Bean设置代理。
7.2.4 doCreateBean(beanName, mbd, args)——创建bean的过程
Bean的创建过程可以分成两个步骤:
1).实例化Bean的对象
2).针对Bean实例做初始化
7.2.4.1 createBeanInstance()——创建bean实例
这个方法主要是对Bean实例的创建,创建方式顺序是BeanDefition的Supplier
>BeanDefinition的FactoryMethod
>构造器反射创建。
7.2.4.2 applyMergedBeanDefinitionPostProcessors()
应用MergedBeanDefinitionPostProcessor
,对MergedBeanDefinition做后处理。
7.2.4.3 addSingletonFactory(beanName, ObjectFactory)
将创建的Bean封装到ObjectFactory中,然后添加到singletonFactories
中(三级缓存)。
这样在出现循环依赖的时候,可以从三级缓存中获取到这个Bean的引用,虽然此时这个Bean还未完全准备好。
7.2.4.4 poplulateBean(beanName, mdb, instanceWrapper)
填充Bean的属性。被创建出来的Bean对象会在这一步被设置属性。
Spring会根据Bean的AutoWireType进行属性的装配。
注意装配的过程中可能发生循环依赖。
7.2.4.5 initializeBean(beanName, exposedObject, mdb)——调用初始化方法
经过7.2.4.4 其实Bean对象已经创建的差不多了,之所以还需要在进行初始化是因为一些Bean可能指定了init方法,做一些特殊的初始化动作。这一步就是为了能够执行这些逻辑。
7.2.4.6 registerDisposableBeanIfNecessary()
为Bean注册销毁时的回调。
7.3 afterSingletonCreation(beanName)——创建后的处理
同样是做一些校验,并且将bean从正在创建的状态中移除。
7.4 addSingleton()——添加单例
这一步Spring会将创建好的Bean添加到singletonObjects中(一级缓存,用来存放已经创建好的bean),并从earlySingletonObjects和singletonFactories(二级缓存和三级缓存)中移除这个bean的引用。
8 校验类型
在getSingleton()执行完成之后,已经得到了bean的对象,这一步主要是针对Bean类型校验,看是否得到的实例是否是期望的类型,如果不是就对类型进行转换。
转换失败,则抛出异常。
9 返回bean
将得到的Bean返回给调用者。完成获取Bean的过程。
通过流程图加深印象:
循环依赖
什么是循环依赖
循环依赖是Spring容器在初始化Bean时,发现两个Bean互相依赖的一种特殊情况。
假设这么一种情况:ComponentA
和ComponentB
为同一容器中的两个bean,且他们装配时都需要装配对方的引用:
@Component
public class ComponentA {
@Autowired
ComponentB b;
}
@Component
public class ComponentB {
@Autowired
ComponentA a;
}
那么在创建ComponentA
时,发现需要装配ComponentB
,转而去创建ComponentB
时,又发现需要装配ComponentA
。
那么是否会引起Spring创建Bean失败呢?Spring是如何解决这种问题的?
Spring解决循环依赖的方式
为了解决这种问题,Spring引入了三级缓存,分别存放不同时期的Bean引用。singletonObjects
:一级缓存用来存放完全初始化完成的Bean。earlySingletonObjects
:二级缓存用来存放实例化,但初始化完成的Bean。sngletonFactories
:三级缓存用来存放ObjectFactory
,ObjectFactory
通常会持有已经实例化完成的Bean引用,并在getObject()
时可能加工一下Bean,并返回Bean引用。
回到之前的问题:
Spring在创建 ComponentA
时,先实例化了ComponentA
,并通过ObjectFactory
持有着ComponentA
的引用。在singletonFactories
中(第三级缓存)添加缓存。然后开始装配 ComponentA
,发现需要依赖ComponentB
,由于ComponentB
在三级缓存中都找不到,于是转而开始创建ComponentB
。同样实例化了 ComponentB
,并在singletonFactories
中添加缓存。开始对 ComponentB
进行装配,发现又需要ComponentA
,Spring第二次去getBean(ComponentA)
这次由于第三级缓存中已经存在了 ComponentA
的缓存,因此能够取到ComponentA
实例的引用。同时,由于此次的获取,三级缓存的关系也发生了变化,第三级缓存singletonFacotries
移除了关于ComponentA
的引用,而是第二级缓存earlySingletonObjects
会放入ComponentA
的引用(因为此时已经通过ObjectFactory
获取过ComponentA
的实例了)。ComponentB
得到了ComponentA
的引用,完成了装配。被添加到第一级缓存singletonObjects
中,表示已经初始化完成,可以供其他Bean使用。回到第一个 getBean(ComponentA)
的地方,因为已经能够得到ComponentB
的引用了,所以ComponentA
也能被正常初始化完成。于是也被添加进了第一级缓存singletonObejcts
中。
通过一个流程图帮助读者加深下印象:
无法解决的循环依赖的情形
上述循环依赖的情况是两个Bean在通过属性注入时产生的,而针对构造器注入的循环依赖Spring是无法解决的。
原因很简单,Spring之所以能解决循环依赖是因为它将Bean的创建过程分成了实例化和初始化两个阶段。实例化完成后Spring就通过三级缓存允许其他Bean先获取到该Bean的缓存。
而针对构造函数注入产生的循环依赖而言,由于在创建阶段就依赖了其他Bean,因此无法先创建实例供其他Bean引用。
疑惑
最后抛出一个问题:为什么解决循环依赖需要三级缓存的设计,个人认为二级缓存也能够解决循环依赖的问题,能否把第二级缓存和第三级缓存合并成一级?
其实是因为二级缓存最主要的一个用途是用作动态代理,如果一个对象需要被动态代理加工。那么当它从三级缓存取出,放入二级缓存的时候,这里的Bean实例会被动态代理包装,返回的是代理类。从而保证后续的过程中,对外暴露的引用是经过代理的引用,实现动态代理的注入。