这么回答循环依赖助力轻松拿下阿里P6
Posted 波波烤鸭
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了这么回答循环依赖助力轻松拿下阿里P6相关的知识,希望对你有一定的参考价值。
一篇文章彻底搞定Spring循环依赖
lecture:波哥
一、什么是循环依赖
看下图:
上图是循环依赖的三种情况,虽然方式有点不一样,但是循环依赖的本质是一样的,就你的完整创建要依赖与我,我的完整创建也依赖于你。相互依赖从而没法完整创建造成失败。
二、构造注入
1. 案例演示
构造注入就是依赖关系在构造方法中关联。
@Component
@Scope(value = "prototype")
public class BeanA
private BeanB beanB;
public BeanA(BeanB beanB)
this.beanB = beanB;
@Component
public class BeanB
private BeanA beanA;
public BeanB(BeanA beanA)
this.beanA = beanA;
然后我们可以通过启动容器来查看,在Spring中构造注入的循环依赖
在Spring中是没有办法解决的。只是通过抛出异常来处理了。
@Configuration
@ComponentScan
public class Demo1Main
public static void main(String[] args)
ApplicationContext ac = new AnnotationConfigApplicationContext(Demo1Main.class);
2.解决方案
那么在Spring中是如何发现实例的Bean对象有循环依赖
的问题,并抛出异常的呢?其实实现逻辑也比较简单,如下图:
实现的过程也很简单,就是在创建对象之前判断该对象在这个临时容器中是否存在,如果不存在就开始创建,如果存在则说明产生了循环依赖
。对象创建完成后就把该对象从容器中移除。对应的看看Spring的源码中的处理。
3.单例分析
先来看单例的处理,进入到getSingleton
方法中。我们需要关注的是两个方法beforeSingletonCreation(beanName)
和afterSingletonCreation(beanName);
beforeSingletonCreation方法
protected void beforeSingletonCreation(String beanName)
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName))
throw new BeanCurrentlyInCreationException(beanName);
上面的代码很清楚的看到,如果已经存在就抛出循环依赖的错误。而且因为是单例,所以容器也没有通过ThreadLocal
来处理了。
对应的afterSingletonCreation
方法就是移走了。
protected void afterSingletonCreation(String beanName)
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName))
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
4.原型分析
原型Bean思路差不多,但是实现方式区别有点大,我们先来看看beforePrototypeCreation(beanName)
和afterPrototypeCreation(beanName);
这两个方法。
// 数据存储在了 ThreadLocal 中
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
new NamedThreadLocal<>("Prototype beans currently in creation");
protected void beforePrototypeCreation(String beanName)
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal == null)
this.prototypesCurrentlyInCreation.set(beanName);
else if (curVal instanceof String)
Set<String> beanNameSet = new HashSet<>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
else
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.add(beanName);
protected void afterPrototypeCreation(String beanName)
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal instanceof String)
this.prototypesCurrentlyInCreation.remove();
else if (curVal instanceof Set)
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.remove(beanName);
if (beanNameSet.isEmpty())
this.prototypesCurrentlyInCreation.remove();
可以看到这两个方法都只是在ThreadLocal
中错了BeanName的写入和移除的操作。并没有做相关的检查,而这个检查其实是在
这样一来Spring中的构造注入的循环依赖针对单例或是原型的处理我们就清楚了!
三、依赖注入
1.案例演示
设值注入就是通过setter方法来完成属性的赋值。
@Component
public class UserD
@Autowired
private UserC userC;
@Component
public class UserC
@Autowired
private UserD userD;
写主方法测试
@Configuration
@ComponentScan
public class Demo2Main
public static void main(String[] args)
ApplicationContext ac = new AnnotationConfigApplicationContext(Demo2Main.class);
System.out.println(ac.getBean(UserC.class));
我们可以看到在单例的情况下,Spring解决了循环依赖
的问题了。
但是在两个对象都是原型的情况下就解决不了了。
@Component
@Scope(value = "prototype")
public class UserC
@Autowired
private UserD userD;
@Component
@Scope(value = "prototype")
public class UserD
@Autowired
private UserC userC;
启动测试我们可以看到出现了循环依赖的错误提示了
当然,如果我们给其中的一个设置为单例,那么循环依赖问题即可解决。原理,我们分析下就清楚了。
2.解决方案
针对依赖注入这种情况下产生的循环依赖
问题,我们的解决方案是提前暴露
。
但是在Spring中对于Bean实例因为AOP的存在,我们可能返回的并不是原始的Bean对象,而是被AOP增强的代理对象,这时在Spring中需要使用到三级缓存来处理。我们具体来看看Spring中的处理方案。
3.原理分析
然后我们来具体看看在Spring中对于依赖注入的循环依赖
的解决。进入到AbstractAutowireCapableBeanFactory
的doCreateBean
方法中。
上面看到了具体创建Bean对象的方法createBeanInstance
方法中,然后在下面中比较重要的两个的方法
那么对于的提前暴露
应该要在populateBean
方法之前实现。往前面找看到了earlySingletonExposure
变量,标识的就是是否支持提前暴露
。
// 判断条件: 1.是否是单例Bean 2.是否支持循环依赖 3.是否是当前正在创建的Bean
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
如果上面的条件成立就会走下面的代码
if (earlySingletonExposure)
// 把 Lambda 表达式存储在了三级缓存中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// 对应的 addSingletonFactory 方法
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects)
// 该单例对象还未创建过
if (!this.singletonObjects.containsKey(beanName))
// 把 上面的 Lambda 表达式 存储在了 三级缓存中
this.singletonFactories.put(beanName, singletonFactory);
// 对应的二级缓存置空
this.earlySingletonObjects.remove(beanName);
// 记录该单例对象名称
this.registeredSingletons.add(beanName);
然后我们需要在AOP创建对应的代理增强对象之后再看是如果来暴露代理对象的。
也就是我们看的关键是getSingleton
方法
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference)
// 从一级缓存中获取对象
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName))
// 没有获取到创建好的Bean单例,同时当前正在创建Bean对象
// 从 二级缓存中获取 半层品 对象
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference)
// 二级缓存中没有需要获取的Bean 半成品对象
synchronized (this.singletonObjects)
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null)
// 一级缓存中没有
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null)
// 二级缓存 没有 就取出之前存储在三级缓存中的 Lambda 表达式
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null)
// 根据Lambda表达式 获取到Bean的 代理对象
singletonObject = singletonFactory.getObject();
// 把Bean的半成品对象存储在二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
// 清除 一级缓存的 对象
this.singletonFactories.remove(beanName);
// 返回对应的半成品对象
return singletonObject;
然后对应的获取代理对象的逻辑是在singletonFactory.getObject()
中。
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean)
Object exposedObject = bean; // 原始Bean
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors())
for (BeanPostProcessor bp : getBeanPostProcessors())
// 通过每个BeanPostProcessor 对Bean对象做后置处理
if (bp instanceof SmartInstantiationAwareBeanPostProcessor)
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
// 递归获取对应的 代理对象
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
// 返回 对应的增强Bean
return exposedObject;
具体逻辑如下图:
当依赖关系完成后。会进入到前面的getSingleton
方法中。
然后还要做对应的二级缓存和一级缓存数据处理
protected void addSingleton(String beanName, Object singletonObject)
synchronized (this.singletonObjects)
// 创建好的单例Bean会存储在 singletonObjects 这个Map 中,也就是一级缓存中
this.singletonObjects.put(beanName, singletonObject);
// 移除三级缓存的Lambda表达式
this.singletonFactories.remove(beanName);
// 移除二级缓存中的Bean的半成品对象,移除提前暴露的对象
this.earlySingletonObjects.remove(beanName);
// 注册 单例
this.registeredSingletons.add(beanName);
到这儿整个Spring中的循环依赖就给大家介绍完成了~有帮助的话记得关注
和三连支持
哦.
以上是关于这么回答循环依赖助力轻松拿下阿里P6的主要内容,如果未能解决你的问题,请参考以下文章
意外被裁,逆境中意外面过阿里3+1,成功拿下阿里Java岗P6 offer
985本3Android程序员40天拿下阿里P6口头offer,面试成功后整理了这些面试思路
985本3Android程序员40天拿下阿里P6口头offer,面试成功后整理了这些面试思路