Spring面试题
Spring如何解决循环依赖?
- Spring通过递归的方式获取目标bean及其所依赖的bean。
- Spring实例化Bean后(此时还未注入属性),如果允许提前暴露,就将这个Bean绑定在ObjectFactory上保存到singletonFactories里。
- 后续如果需要这个bean的引用,则从singletonFactories里面获取,然后保存到二级缓存里,以后就能直接从二级缓存获取,不用再去访问三级缓存了。
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
...
// 从上至下 分表代表这“三级缓存”
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三级缓存
...
/** Names of beans that are currently in creation. */
// 这个缓存也十分重要:它表示bean创建过程中都会在里面呆着~
// 它在Bean开始创建时放值,创建完成时会将其移出~
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
/** Names of beans that have already been created at least once. */
// 当这个Bean被创建完成后,会标记为这个 注意:这里是set集合 不会重复
// 至少被创建了一次的 都会放进这里~~~~
private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
}
复制代码
singletonObjects
:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用earlySingletonObjects
: 提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖singletonFactories
:单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖
创建Bean
protected Object doCreateBean(){
// Bean实例化...
// 是否要提前暴露(允许循环依赖) 现在此处A是被允许的
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
// 允许暴露,就把A绑定在ObjectFactory上,注册到三级缓存singletonFactories里面去保存着
// Tips:这里后置处理器的getEarlyBeanReference方***被促发,自动代理创建器在此处创建代理对象(注意执行时机 为执行三级缓存的时候)
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 属性赋值
populateBean(beanName, mbd, instanceWrapper);
// 初始化Bean
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
复制代码
- Bean实例化
- 如果Bean允许提前暴露,则把Bean绑定在ObjectFactory上,注册到三级缓存singletonFactories里面去保存着
- 后续如果要获取这个Bean引用,则从三级缓存singletonFactories里获取,然后保存到二级缓存earlySingletonObjects,然后就可以给属性赋值啦!
获取单例Bean的源码如下:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
...
@Override
@Nullable
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 先从一级缓存singletonObjects中去获取
Object singletonObject = this.singletonObjects.get(beanName);
// 如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()),那就再从二级缓存earlySingletonObjects中获取。
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
// 如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。就从三级缓存singletonFactory.getObject()获取。
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// 如果获取到了就从singletonFactories中移除,并且放进earlySingletonObjects。其实也就是从三级缓存移动到了二级缓存
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
...
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}
protected boolean isActuallyInCreation(String beanName) {
return isSingletonCurrentlyInCreation(beanName);
}
...
}
复制代码
步骤:
- 先从
一级缓存singletonObjects
中去获取。(如果获取到就直接return) - 如果获取不到或者对象正在创建中(
isSingletonCurrentlyInCreation()
),那就再从二级缓存earlySingletonObjects
中获取。(如果获取到就直接return) - 如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过
getObject()
获取。就从三级缓存singletonFactory
.getObject()获取。(如果获取到了就从**singletonFactories**
中移除,并且放进**earlySingletonObjects**
。其实也就是从三级缓存**移动(是剪切、不是复制哦~)**
到了二级缓存)
加入
singletonFactories
三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决
暖心小哥哥来总结一下
- Bean实例化(这没啥好说的)
- 实例化完成以后,如果这个Bean允许提前暴露,那么将这个Bean绑定在ObjectFactory上,注册到三级缓存singletonFactories里面去保存着。
- 后续循环依赖如果要用到这个Bean的属性值,那么先从三级缓存里获取,然后将它移动到二级缓存,然后就可以获得引用啦!
ps 个人学习的一些疑问
Q:为什么要先放到三级缓存,而不直接放到二级缓存呢?
A:主要是为了提供AOP支持。循环依赖设置属性的时候,设置的应该是代理对象而不是普通对象,所以要提前进行AOP。所以一个Bean实例化以后,会放一个lambda表达式到三级缓存里。后续设置属性的时候,会调用三级缓存里的lambda表达式提前生成Aop代理对象,然后放到二级缓存里。
Spring中Bean的生命周期
SpringIOC流程
- BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每一个解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中;
- 容器扫描BeanDefinitionRegistry中的BeanDefinition;调用InstantiationStrategy进行Bean实例化的工作;使用BeanWrapper完成Bean属性的设置工作;
Spring AOP原理
一个类初始化完成后,会循环调用所有的BeanPostProcessor方法(可以看一下Bean的生命周期),AbstractAdvisorAutoProxyCreator(代理创建类)继承了BeanPostProcessor,所以会在此时调用它。
AbstractAdvisorAutoProxyCreator 根据是否实现了接口之类的,返回一个jdk动态代理对象或者cglib代理对象。
IOC容器把这个代理对象保存到SingtonObjectMap里面。所以我们以后用到的都是这个代理类。