面试的重点,大厂必问之一
什么是循环依赖
看下图
上图是循环依赖的三种情况,虽然方式有点不一样,但是循环依赖的本质是一样的,就你的完整创建要依赖与我,我的完整创建也依赖于你。相互依赖从而没法完整创建造成失败。
代码演示
我们再通过代码的方式来演示下循环依赖的效果
public class CircularTest {
public static void main(String[] args) {
new CircularTest1();
}
}
class CircularTest1{
private CircularTest2 circularTest2 = new CircularTest2();
}
class CircularTest2{
private CircularTest1 circularTest1 = new CircularTest1();
}
执行后出现了 StackOverflowError 错误
上面的就是最基本的循环依赖的场景,你需要我,我需要你,然后就报错了。而且上面的这种设计情况我们是没有办法解决的。那么针对这种场景我们应该要怎么设计呢?这个是关键!
分析问题
首先我们要明确一点就是如果这个对象A还没创建成功,在创建的过程中要依赖另一个对象B,而另一个对象B也是在创建中要依赖对象A,这种肯定是无解的,这时我们就要转换思路,我们先把A创建出来,但是还没有完成初始化操作,也就是这是一个半成品的对象,然后在赋值的时候先把A暴露出来,然后创建B,让B创建完成后找到暴露的A完成整体的实例化,这时再把B交给A完成A的后续操作,从而揭开了循环依赖的密码。也就是如下图:
解决
明白了上面的本质后,我们可以自己来尝试解决下:
先来把上面的案例改为set/get来依赖关联
public class CircularTest {
public static void main(String[] args) throws Exception{
System.out.println(getBean(CircularTest1.class).getCircularTest2());
System.out.println(getBean(CircularTest2.class).getCircularTest1());
}
private static <T> T getBean(Class<T> beanClass) throws Exception{
// 1.获取 实例对象
Object obj = beanClass.newInstance();
// 2.完成属性填充
Field[] declaredFields = obj.getClass().getDeclaredFields();
// 遍历处理
for (Field field : declaredFields) {
field.setAccessible(true); // 针对private修饰
// 获取成员变量 对应的类对象
Class<?> fieldClass = field.getType();
// 获取对应的 beanName
String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
// 给成员变量赋值 如果 singletonObjects 中有半成品就获取,否则创建对象
field.set(obj,getBean(fieldClass));
}
return (T) obj;
}
}
class CircularTest1{
private CircularTest2 circularTest2;
public CircularTest2 getCircularTest2() {
return circularTest2;
}
public void setCircularTest2(CircularTest2 circularTest2) {
this.circularTest2 = circularTest2;
}
}
class CircularTest2{
private CircularTest1 circularTest1;
public CircularTest1 getCircularTest1() {
return circularTest1;
}
public void setCircularTest1(CircularTest1 circularTest1) {
this.circularTest1 = circularTest1;
}
}
然后我们再通过把对象实例化和成员变量赋值拆解开来处理。从而解决循环依赖的问题
public class CircularTest {
// 保存提前暴露的对象,也就是半成品的对象
private final static Map<String,Object> singletonObjects = new ConcurrentHashMap<>();
public static void main(String[] args) throws Exception{
System.out.println(getBean(CircularTest1.class).getCircularTest2());
System.out.println(getBean(CircularTest2.class).getCircularTest1());
}
private static <T> T getBean(Class<T> beanClass) throws Exception{
//1.获取类对象对应的名称
String beanName = beanClass.getSimpleName().toLowerCase();
// 2.根据名称去 singletonObjects 中查看是否有半成品的对象
if(singletonObjects.containsKey(beanName)){
return (T) singletonObjects.get(beanName);
}
// 3. singletonObjects 没有半成品的对象,那么就反射实例化对象
Object obj = beanClass.newInstance();
// 还没有完整的创建完这个对象就把这个对象存储在了 singletonObjects中
singletonObjects.put(beanName,obj);
// 属性填充来补全对象
Field[] declaredFields = obj.getClass().getDeclaredFields();
// 遍历处理
for (Field field : declaredFields) {
field.setAccessible(true); // 针对private修饰
// 获取成员变量 对应的类对象
Class<?> fieldClass = field.getType();
// 获取对应的 beanName
String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
// 给成员变量赋值 如果 singletonObjects 中有半成品就获取,否则创建对象
field.set(obj,singletonObjects.containsKey(fieldBeanName)?
singletonObjects.get(fieldBeanName):getBean(fieldClass));
}
return (T) obj;
}
}
运行程序你会发现问题完美的解决了
在上面的方法中的核心是getBean方法,Test1 创建后填充属性时依赖Test2,那么就去创建 Test2,在创建 Test2 开始填充时发现依赖于 Test1,但此时 Test1 这个半成品对象已经存放在缓存到 singletonObjects
中了,所以Test2可以正常创建,在通过递归把 Test1 也创建完整了。
最后总结下该案例解决的本质:
Spring循环依赖
针对Spring中Bean对象的各种场景。支持的方案不一样:
Spring 中哪些情况下,不能解决循环依赖问题?
然后我们再来看看Spring中是如何解决循环依赖问题的呢?刚刚上面的案例中的对象的生命周期的核心就两个
而Spring创建Bean的生命周期中涉及到的方法就很多了。下面是简单列举了对应的方法
基于前面案例的了解,我们知道肯定需要在调用构造方法方法创建完成后再暴露对象,在Spring中提供了三级缓存来处理这个事情,对应的处理节点如下图:
对应到源码中具体处理循环依赖的流程如下:
上面就是在Spring的生命周期方法中和循环依赖出现相关的流程了。那么源码中的具体处理是怎么样的呢?我们继续往下面看。
首先在调用构造方法的后会放入到三级缓存中
下面就是放入三级缓存的逻辑
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
// 使用singletonObjects进行加锁,保证线程安全
synchronized (this.singletonObjects) {
// 如果单例对象的高速缓存【beam名称-bean实例】没有beanName的对象
if (!this.singletonObjects.containsKey(beanName)) {
// 将beanName,singletonFactory放到单例工厂的缓存【bean名称 - ObjectFactory】
this.singletonFactories.put(beanName, singletonFactory);
// 从早期单例对象的高速缓存【bean名称-bean实例】 移除beanName的相关缓存对象
this.earlySingletonObjects.remove(beanName);
// 将beanName添加已注册的单例集中
this.registeredSingletons.add(beanName);
}
}
}
然后在填充属性的时候会存入二级缓存中
earlySingletonObjects.put(beanName,bean);
registeredSingletons.add(beanName);
最后把创建的对象保存在了一级缓存中
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 将映射关系添加到单例对象的高速缓存中
this.singletonObjects.put(beanName, singletonObject);
// 移除beanName在单例工厂缓存中的数据
this.singletonFactories.remove(beanName);
// 移除beanName在早期单例对象的高速缓存的数据
this.earlySingletonObjects.remove(beanName);
// 将beanName添加到已注册的单例集中
this.registeredSingletons.add(beanName);
}
}
级缓存如何实现
1、解决代理问题
因为默认情况下,代理是通过BeanPostProcessor来完成,为了解决代理,就需要提前创建代理,那么这个代理的创建就放到3级缓存中来进行创建。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
getEarlyBeanReference 此方法会返回代理bean
2、解决单例通过第3级缓存多次获取的值不一致
从上图中可知,对象是先从 一级->二级->三级缓存 这样查找,当三级缓存产生了对象后就放入二级缓存中缓存起来,同时删除三级缓存。
3、流程图
疑问点
这些疑问点也是面试官喜欢问的问题点
为什么需要三级缓存
三级缓存主要处理的是AOP的代理对象,存储的是一个ObjectFactory
三级缓存考虑的是代理对象,而二级缓存考虑的是性能-从三级缓存的工厂里创建出对象,再扔到二级缓存(这样就不用每次都要从工厂里拿)
没有三级环境能解决吗?
没有三级缓存是可以解决循环依赖问题的
三级缓存分别什么作用
一级缓存:正式对象
二级缓存:半成品对象
三级缓存:工厂
// 从上至下 分表代表这“三级缓存”
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); // 三级缓存
1.singletonObjects:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用
2.earlySingletonObjects:提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖
3.singletonFactories:单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖
1.AbstractBeanFactory 中的 doGetBean()方法
2.DefaultSingletonBeanRegistry中的 getSingleton()方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory=this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
先从一级缓存singletonObjects中去获取。(如果获取到就直接return)
如果获取不到或者对象正在创建中(isSingletonCurrentlyInCreation()),那就再从二级缓存earlySingletonObjects中获取。(如果获取到就直接return)
如果还是获取不到,且允许singletonFactories(allowEarlyReference=true)通过getObject()获取。就从三级缓存singletonFactory.getObject()获取。(如果获取到了就从singletonFactories中移除,并且放进earlySingletonObjects。其实也就是从三级缓存移动(是剪切、不是复制哦~)到了二级缓存)此处的移动保证了,之后在init时候仍然是同一个对象
3.AbstractAutowireCapableBeanFactory.createBean/doCreateBean()
实例化
// 使用构造器/工厂方法 instanceWrapper是一个BeanWrapper
instanceWrapper = createBeanInstance(beanName, mbd, args);
// 此处bean为"原始Bean" 也就是这里的A实例对象:A@1234
final Object bean = instanceWrapper.getWrappedInstance();
添加到三级缓存
// 允许暴露,就把A绑定在ObjectFactory上,注册到三级缓存singletonFactories里面去保存着
// Tips:这里后置处理器的getEarlyBeanReference方法会被促发,自动代理创建器在此处创建代理对象(注意执行时机 为执行三级缓存的时候)
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
//注意 此时加入的是一个factory没有执行
4.属性赋值
//此时候上面说到的getEarlyBeanReference方法就会被执行。这也解释为何我们@Autowired是个代理对象,而不是普通对象的根本原因
populateBean(beanName, mbd, instanceWrapper);
解决循环依赖,在获取单例对象时
singletonFactory.getObject()调用了
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
private final Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
/* 这里,主要就是看看到底要不要生成代理对象,要的话,就生成,不要就算了,另外,做了个标记:在earlyProxyReferences加了当前bean的key,表示:当前bean,已经被getEarlyBeanReference方法处理过了。
/至于,最终到底有没有生成代理对象,另说。毕竟调用wrapIfNecessary也不是说,一定就满足切面,要生成代理对象。
可能返回的仍然是原始对象。*/
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
此处的SmartInstantiationAwareBeanPostProcessor继承自BeanPostProcessor
BeanPostProcessor接口会在init阶段生成对对象的代理,getCacheKey
保证不会重复生成代理对象
5.初始化对象
如果有代理会检查是否发生了循环依赖
exposedObject = initializeBean(beanName, exposedObject, mbd);
... // 至此,相当于A@1234已经实例化完成、初始化完成(属性也全部赋值了~)
// 这一步我把它理解为校验:校验:校验是否有循环引用问题~~~~~
if (earlySingletonExposure) {
// 注意此处第二个参数传的false,表示不去三级缓存里singletonFactories再去调用一次getObject()方法了~~~
// 上面建讲到了由于B在初始化的时候,会触发A的ObjectFactory.getObject() 所以a此处已经在二级缓存earlySingletonObjects里了
// 因此此处返回A的实例:A@1234
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 这个等式表示,exposedObject若没有再被代理过,这里就是相等的
// 显然此处我们的a对象的exposedObject它是没有被代理过的 所以if会进去~
// 这种情况至此,就全部结束了~~~
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
// 继续以A为例,比如方法标注了@Aysnc注解,exposedObject此时候就是一个代理对象,因此就会进到这里来
//hasDependentBean(beanName)是肯定为true,因为getDependentBeans(beanName)得到的是["b"]这个依赖
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
// A@1234依赖的是["b"],所以此处去检查b
// 如果最终存在实际依赖的bean:actualDependentBeans不为空 那就抛出异常 证明循环引用了~
for (String dependentBean : dependentBeans) {
// 这个判断原则是:如果此时候b并还没有创建好,this.alreadyCreated.contains(beanName)=true表示此bean已经被创建过,就返回false
// 若该bean没有在alreadyCreated缓存里,就是说没被创建过(其实只有CreatedForTypeCheckOnly才会是此仓库)
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
是否可以没有二级缓存
如果只在两个对象AB产生循环依赖时,可以不需要。
但是如果是ABC产生循环依赖时
@Service
public class TestService1 {
@Autowired
private TestService2 testService2;
@Autowired
private TestService3 testService3;
public void test1() {
}
}
@Service
public class TestService2 {
@Autowired
private TestService1 testService1;
public void test2() {
}
}
@Service
public class TestService3 {
@Autowired
private TestService1 testService1;
public void test3() {
}
}
TestService1注入到TestService3又需要从第三级缓存中获取实例,而第三级缓存里保存的并非真正的实例对象,而是ObjectFactory对象。说白了,两次从三级缓存中获取都是ObjectFactory对象,而通过它创建的实例对象每次可能都不一样的。
这样不是有问题?
为了解决这个问题,spring引入的第二级缓存。其实TestService1对象的实例已经被添加到第二级缓存中了,而在TestService1注入到TestService3时,只用从第二级缓存中获取该对象即可。
假设只有singletonObjects和singletonFactories可否完成循环依赖?
由图中可知也是不可以实现的。
是否需要三级缓存
如果创建的Bean有对应的代理,那其他对象注入时,注入的应该是对应的代理对象;但是Spring无法提前知道这个对象是不是有循环依赖的情况,而正常情况下(没有循环依赖情况),Spring都是在创建好完成品Bean之后才创建对应的代理。这时候Spring有两个选择:
(1)不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。
(2)不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖`的情况下,Bean就可以按着Spring设计原则的步骤来创建。
由上图可知,对象存在代理时,2级缓存无法解决问题。因为代理对象是通过BeanPostProcessor来完成,是在设置属性之后才产生的代理对象。
此时可能有人会说,那如果我在构建完B的实例后,就立马进行Aop代理,这样不就解决问题了吗?那假设A和B之间没有发生循环依赖,这样设计会不会不优雅?(方式1)
Spring选择了第二种方式,那怎么做到提前曝光对象而又不生成代理呢?
Spring就是在对象外面包一层ObjectFactory,提前曝光的是ObjectFactory对象,在被注入时才在ObjectFactory.getObject方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存Map<String, Object> earlySingletonObjects。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
知识来源:马士兵教育
Spring的三级缓存_spring三级缓存_普通网友的博客-CSDN博客