java 培训高频 Spring 面试题分享
Spring 怎么解决循环依赖问题?
对于这个问题,估计游戏人人看了网上很多博客还是很头大,就看到了一堆头大的源码,感觉很复杂的样子,其实没有那么复杂。本篇文章,先大白话告诉你 Spring 到底怎么解决循环依赖的,毕竟没人愿意看源码,先来看看什么是循环依赖。
什么是循环依赖
循环依赖的定义是非常好理解的,就是 A 依赖 B,B 也依赖 A,或者 A 依赖本身。
简单来讲就是之前看到的一个段子,一个应聘者去找工作,企业都要求有相关工作经验,但是应聘者找不到工作就没有工作经验,而没有工作经验的人企业不要,卡死了。在 Spring 中代码表示如下
@Component
public class A {
// A 中注入了 B
@Autowired
private B b;
}
@Component
public class B {
// B 中注入了 A
@Autowired
private A a;
}
还有自己依赖自己的
@Component
public class A {
@Autowired
private A a;
}
Spring 是如何解决循环依赖的,任何形式的循环依赖都可以解决吗?
先说下下答案,Spring 是通过三级缓存解决循环依赖的,既然是基于缓存,那 prototype(Bean 的一种作用域,忘了可以看看前面的文章)的 bean 所造成的循环依赖肯定是不行了,因为 Spring 并没有缓存 prototype 的 bean。那其他的循环依赖就都能解决了吗?
答案是否定的,在解释这个之前需要先了解 Spring 在创建 Bean 的过程。
Spring 创建 Bean 的过程主要有三步,
实例化(createBeanInstance 方法)
属性注入(populateBean 方法)
初始化(initializeBean 方法)
而常用的 Bean 注入方法有构造方法注入和属性注入,这两者有什么区别呢?
构造方法注入
@Component
public class A {
public A(B b) {
}
}
@Component
public class B {
public B(A a) {
}
}
构造方法注入步骤:
调用 B 的构造方法
调用 A 的构造方法
不出意外已经卡死了
属性注入
@Service
public class A { //1
@Autowired
private B b; //2
}
@Service
public class B { //3
@Autowired
public A a; //4
}
属性注入的步骤:
调用 A 的构造方法
检查依赖的对象(B)
调用 B 的构造方法
使用注解 @Autowired 将 b 注入给 A
对于不理解为什么是这个步骤的小伙伴,可以去看看类实例化时什么时候执行构造方法
从上述构造方法注入和属性注入的步骤来看,通过构造方法注入的 bean 所导致的循环依赖是无法解决的,因为构造方法注入时,要想先实例化 A,那么依赖 B 必须先实例化,但是 B 的实例化也会依赖 A。而采用属性注入 Bean 时,不需要保证先实例化 B,可以先实例化 A,再实例化 B,然后再将 b 注入到 A 中。所以 Spring 可以解决的是通过属性注入的 Bean 所引起的循环依赖_java培训。
那具体是怎么实现的呢?流程如下,前四步对应代码中的 1,2,3,4
创建 A 的 bean,这时先调用 A 的构造方法实例化,也就是上面创建 Bean 的第一步,所以到这里创建了一个 A 的半成品的 Bean
这时候需要将 B 的 Bean 注入到 A 中,需要先实例化 B(未完成属性注入)
调用 B 构造方法进行实例化,也是个半成品的 bean
将 A 的半成品的注入到 B 中,完成了属性注入,然后初始化。但这里 B 的 bean 已经是一个完整的 bean 了(创建 bean 的三步都完成了)
回到第 2 步,将 B 的 bean 注入到属性 b 中,然后初始化,A 也是一个完整的 bean 了
流程图如下:
其实简单来说就是,创建 bean 需要三步,A 先完成了第一步,这时需要 B 的 bean,去找 B 要。这时 B 的 bean 也没创建,也先完成了第一步实例化,第二步将 A 的只完成第一步的 bean 注入到属性中了,然后初始化,B 的 bean 创建完成。B 将 bean 给 A,A 也完成了属性注入及初始化。到此循环依赖结束。
就靠这个创建一半的 bean 解决了循环依赖问题,构造方法注入为啥不行,因为它不能先完成创建 bean 的第一步,必须先要 B 的 bean。
这个创建一般的 bean 就是具体就是靠三级缓存实现的,在看源码之前,还有一个问题需要解决。
是否只要是构造方法注入的 bean 所产生的循环依赖就是无法解决的呢?
答案是否定的,上面上说的是两个 bean 的注入方式都是构造函数注入,这个确实解决不了,那一个是构造方法注入,一个是属性注入,这种循环依赖可以解决吗?
答案是不一定的,得看先创建的 bean 中的注入方式是什么,如果先创建的 bean 的注入方式是构造方法注入,那开始就无法提供半成品的 bean,这种循环依赖是无法解决的。如果先创建的 bean 中的注入方法是属性注入,后创建 bean 的注入方法是构造方法注入,这种循环依赖是可以解决的,总结如下,A 和 B 为上面代码中的类
依赖注入方式是否可以解决两个都是构造方法注入否两个都是属性注入是 A 中的注入方式是构造方法注入,B 中的注入方式是属性注入否 A 中的注入方式是属性注入,B 中的注入方式是构造方法注入是
扩展
如果是三个 bean 相互依赖呢?A 依赖 B,B 依赖 C,C 依赖 A,A 中是属性注入,B 中是构造方法注入,C 中是属性注入,这种循环依赖可以解决吗?(想不出来就再把上面的看一遍)
最后要看源码了,看看三级缓存是怎么实现这个半成品 bean 的获取的,不想看的小伙伴可以跳过了
解决循环依赖的 Spring 源码
既然是创建 bean,那就要从 getBean 方法开始,点进去是这样子的,
public Object getBean(String name, Object... args) throws BeansException {
return this.doGetBean(name, (Class)null, args, false);
}
继续查看 doGetBean 方法,如下,这个方法非常的长,只拿出来了一部分
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
String beanName = this.transformedBeanName(name);
Object sharedInstance = this.getSingleton(beanName);
Object bean;
.......
if (mbd.isSingleton()) {
sharedInstance = this.getSingleton(beanName, () -> {
try {
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throw var5;
}
});
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
.......
比较重要的是 getSingleton 方法,通过 beanName 获取 bean,点击去是这个方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName); //一级缓存
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
synchronized(this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName); //二级缓存
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName); //三级缓存
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName); //二级缓存获取 bean 后,从三级缓存删除
}
}
}
}
return singletonObject;
}
从上述代码可以看出来 getSingleton(String beanName, boolean allowEarlyReference)的作用是从缓存中获取 bean,一共有三级缓存
singletonObjects,一级缓存,存储的是所有创建好了的单例 bean,其实就是个 Map,其定义是这样的,private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
earlySingletonObjects,二级缓存,完成实例化,但是还未进行属性注入及初始化的对象,其定义是这样的,private final Map<String, Object> earlySingletonObjects = new HashMap(16);
singletonFactories,三级缓存,提前暴露的一个单例工厂,二级缓存中存储的就是从这个工厂中获取到的对象,其定义是这样的,private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
依次从一级缓存获取 bean,但因为是第一次创建 Bean,显然三个缓存中都没有,那 singletonObject 应该是 null,这就又回到上面的 doGetBean 方法 if 语句中的 getSingleton 方法,点进去是这样的_java视频
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized(this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
this.beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = this.suppressedExceptions == null;
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet();
}
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
} catch (IllegalStateException var16) {
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw var16;
}
} catch (BeanCreationException var17) {
BeanCreationException ex = var17;
if (recordSuppressedExceptions) {
Iterator var8 = this.suppressedExceptions.iterator();
while(var8.hasNext()) {
Exception suppressedException = (Exception)var8.next();
ex.addRelatedCause(suppressedException);
}
}
throw ex;
} finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
this.afterSingletonCreation(beanName);
}
if (newSingleton) {
this.addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
上述代码执行完,又回到了最上面的 doGetBean 方法 if 语句中的 createBean 方法,点进去是这样的
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Creating instance of bean '" + beanName + "'");
}
RootBeanDefinition mbdToUse = mbd;
Class<?> resolvedClass = this.resolveBeanClass(mbd, beanName, new Class[0]);
if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
mbdToUse = new RootBeanDefinition(mbd);
mbdToUse.setBeanClass(resolvedClass);
}
try {
mbdToUse.prepareMethodOverrides();
} catch (BeanDefinitionValidationException var10) {
throw new BeanDefinitionStoreException(mbdToUse.getResourceDescription(), beanName, "Validation of method overrides failed", var10);
}
Object beanInstance;
try {
beanInstance = this.resolveBeforeInstantiation(beanName, mbdToUse);
if (beanInstance != null) {
return beanInstance;
}
} catch (Throwable var11) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", var11);
}
try {
beanInstance = this.doCreateBean(beanName, mbdToUse, args); //在这里
if (this.logger.isDebugEnabled()) {
this.logger.debug("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
} catch (BeanCreationException var7) {
throw var7;
} catch (ImplicitlyAppearedSingletonException var8) {
throw var8;
} catch (Throwable var9) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", var9);
}
}
从代码中找到 doCreateBean 方法,点进去,是这样的,太长了,省略一部分
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = this.createBeanInstance(beanName, mbd, args); //上面介绍创建 bean 的第一步,实例化
}
Object bean = instanceWrapper.getWrappedInstance();
Class<?> beanType = instanceWrapper.getWrappedClass();
if (beanType != NullBean.class) {
mbd.resolvedTargetType = beanType;
}
synchronized(mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
this.applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
} catch (Throwable var17) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Post-processing of merged bean definition failed", var17);
}
mbd.postProcessed = true;
}
}
boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
}
this.addSingletonFactory(beanName, () -> { //添加到三级缓存中
return this.getEarlyBeanReference(beanName, mbd, bean);
});
}
Object exposedObject = bean;
try {
this.populateBean(beanName, mbd, instanceWrapper); //前面提到的创建 bean 的第二步,属性注入
exposedObject = this.initializeBean(beanName, exposedObject, mbd); //前面提到的创建 bean 的第二步,初始化
} catch (Throwable var18) {
if (var18 instanceof BeanCreationException && beanName.equals(((BeanCreationException)var18).getBeanName())) {
throw (BeanCreationException)var18;
}
throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", var18);
}
.......
上面代码中可以看到,bean 的创建在第一步实力化后,被放到了三级缓存中,然后再进行的属性注入和初始化,这也就是说,bean 的创建再完成第一步后就可以放到缓存中,供需要它的地方注入,这时缓存中的 bean 也就是前面说的半成品的 bean。
整个过程看代码挺复杂,其实过程没有那么复杂,就是先从 getBean 获取 bean,依次从一级、二级、三级缓存中获取,都没获取到说明这个 bean 没有被创建过,那就去创建 bean,创建 bean 需要三步,完成第一步就可以作为半成品的 bean 放到缓存中,供需要的地方获取。
Spring 就是这么解决循环依赖的,是不是也不复杂,但其实还有挺多细节可以看,比如要用三级缓存,二级缓存行不行?为什么第三级缓存获取 bean 后要放到二级缓存中,然后从第三级中删除?
当然,肯定有人是懒得看代码的,那面试中问到 Spring 是如何解决循环依赖的要怎么回答呢?下面进行一个简单的总结
面试中如何简短地回答 Spring 是如何解决循环依赖的
Spring 是通过三级缓存解决的循环依赖问题,创建 bean 的过程分为实例化、属性注入、初始化三步。假设 A 和 B 发生了循环依赖,A 完成实例化后会放到缓存中,这时需要在 A 中注入 B 的 bean。对 B 进行实例化,这时需要在 B 中注入 A 的 bean,从缓存中获取 A 实例化后的 bean,B 的 bean 创建完成。A 从缓存中获取 B 的 bean,完成属性注入和初始化。
文章转载来源于路人 zhang
评论