Spring之循环依赖详解
2023-04-03 15:04:47
Spring之循环依赖问题一直是Spring其中一个非常重要的话题,一方面是因为源代码做了很多处理来解决循环依赖,另一方面是因为这也是很多互联网公司面试时的高频面试问题。
在探索这个问题之前,我们必须弄清楚什么是循环依赖于Spring。我们只是从字面上理解,那就是同时,A依赖B,B也依赖A,完成了一个依赖循环。
这就是代码层次的体现:
@Component
public class A {
// B注入A
@Autowired
private B b;
}
@Component
public class B {
// A也注入到B中
@Autowired
private A a;
}
当然,这是最常见的循环依赖,更特殊
// 依靠自己
@Component
public class A {
// A注入A
@Autowired
private A a;
}
虽然表现形式不同,但实际上是循环依赖的问题。
Spring有解决循环依赖的前提条件:
1.Bean有循环依赖,必须是单例
2.依靠注入的方式不能全部注入构造器
接下来是Spring bean的创建本质上是一个对象的创建,一个完整的对象包括两部分:当前对象的实例化和对象属性的实例化。Spring bean也不例外。
在在Spring中,物体的实例化是通过反射实现的,物体的属性是在物体实例化后以某种方式设置的。这是了解循环依赖的催化剂,这里有两个类别以A和B为例进行解释,以下是A和B的声明:
@Component
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
@Component
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
从上面的代码可以看出,这里A和B各自以对方为自己的全局属性。这里首先要说明的是,Spring实例化bean是通过applicationcontextex.getBean()进行方法。如果要获得的对象依赖于另一个对象,它将首先创建当前对象,然后通过递归调用applicationcontextextextextex.getBean()获取所依赖对象的方法,最后将获得对象注入当前对象。
在这里,我们将首先初始化上述内容以A对象实例为例进行讲解。首先,Spring试图通过Applicationcontextttextion。.getBean()获取A对象实例的方法,因为Spring容器中没有A对象实例,所以它会创建A对象。然后发现了它的依赖因此,B对象会尝试通过Aplicationcontextextet递归.getBean()获取B对象实例的方法。
但是此时Spring容器中没有B对象的例子,因此它将首先创建B对象的例子。
我们这个时间点要注意,这个时候A对象和B对象已经创建并保存在Spring容器中,但A对象的属性B和B对象的属性A尚未设置。在前面Spring创建B对象后,Spring发现B对象依赖属性A,因此仍将尝试将ApplicationContextext转回.getBean()获取A对象实例的方法。因为Spring中已经有一个A对象的例子。虽然只是半成品(其属性b尚未初始化),但仍然是目标bean,所以会返回A对象的例子。
此时,将B对象的属性a设置为Aplicationcontexttextte.getBean()方法递归返回,即B对象的实例返回,此时将该实例设置在A对象的属性B中。与此同时,注意A对象的属性B和B对象的属性A都设置了目标对象的实例。
这里的A对象实际上与之前设置在实例B中的半成品A对象相同,引用地址相同。这里为A对象的B属性设置了值,实际上是为半成品的A属性设置了值。
我们需要注意的一点,Spring是如何标记开始生成的A对象是一个半成品,以及如何保存A对象的?这里的标记工作Spring是使用Aplicationcontext的属性Setsingletonscureng。tlyincreation用于保存,而半成品的A对象用MapsingletonFactories保存。这里的ObjectFactory是一个工厂对象,可以通过调用其getobject()来获得目标对象。在AbstractBeanFactory。.doGetBean()获取对象的方法如下:
protected T doGetBean(final String name, @Nullable final Class requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 试着通过bean名获得目标bean对象,比如这里的a对象
Object sharedInstance = getSingleton(beanName);
// 我们这里的目标对象都是单例的
if (mbd.isSingleton()) {
// 在这里,我们试着创建目标对象。第二个参数是ObjectFactory类型的对象,它使用Java8lamadada
// 表达式写作,只要上述getsingleton()方法的返回值为空,就会调用这里的getsingleton()方法创建
// 目标对象
sharedInstance = getSingleton(beanName, () -> {
try {
// 试着创建目标对象
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
throw ex;
}
});
}
return (T) bean;
}
这里的doGetBean()方法是一种非常关键的方法,第一步getSingleton()方法的作用是试图从缓存中获得目标对象。如果没有,试着获得半成品的目标对象;如果第一步没有获得目标对象的例子,则进入第二步。第二步getSingleton()方法的作用是尝试创建目标对象,并将其所依赖的属性注入对象。这实际上是主要逻辑。我们前面的图表中已经注明了三次dogetbean()方法将在整个过程中被调用。第一次调用的时候会尝试获得以A对象为例,此时走的是第一个getsingleton()方法,因为没有创建A对象的成品或半成品,所以这里得到的是null。然后会调用第二个getSingleton()方法,创建A对象的例子,然后将dogetbean()方法递归,尝试获取B对象的例子并注入A对象。此时由于Spring容器中没有B对象的成品或半成品,所以它仍然会去第二个getsingleton()方法,在这种方法中创建B对象的例子。
创建完成后,试图获得它所依赖的东西A的实例作为其属性,因此仍将递归调用dogetbean()方法。由于前面已经有一个半成品了当A对象的例子再次尝试获得A对象的例子时,它将采用第一个getsingleton()方法。在这种方法中,将获得半成品A对象的实例,然后返回实例,并将其注入B对象的属性a,此时B对象的实例化完成。然后,实例化完成B对象递归返回,此时将该实例注入A对象,从而获得成品A对象。
我们可以在这里阅读上面的第一个getSingleton()方法:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 试着从缓存中获得成品的目标对象,如果存在,直接返回
Object singletonObject = this.singletonObjects.get(beanName);
// 如果缓存中没有目标对象,则判断当前对象是否处于创建过程中。在前面的解释中,第一次尝试获得A对象
// 在实例之后,A对象将被标记为正在创建中,因此当最终尝试获得A对象时,这里的if判断将是true/span>
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 这里的singletonFactories是Map,其key是bean的名字,值是objectFactory类型
// 对象,对于A和B来说,A和B对象的例子是通过调用图其getobject()来返回的,不管是不是半成品。
ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 获取目标对象的例子
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
这里我们会有一个问题,那就是我们在这里会有一个问题A的半成品实例是如何实例化的,然后是如何将其包装成Objectfactory类型的对象,并将其放入上述singletonfactories属性中。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
try {
// 在初始化实例之后,这里就是判断bean是否依赖于其他bean,如果依赖的话,
// 将递归调用getbean()方法试图获得目标bean
populateBean(beanName, mbd, instanceWrapper);
} catch (Throwable ex) {
// 省略...
}
return exposedObject;
}
到这里,Spring解决循环依赖问题的整个实现思路豁然开朗。对于整个过程,我们只需要理解两点:
1.Spring通过递归获得目标bean及其所依赖的bean;
2.Spring实例化一个bean时,分两步进行,先实例化目标bean,再注入属性。
结合这两点,可以知道当Spring实例化一个bean时,它是所有bean所依赖的第一个递归实例化,直到bean不依赖其他bean,此时将返回实例,然后将获得的bean设置为每个上层bean的属性。
读完这篇文章后,是的Spring