概述
在介绍 Spring 是如何解决循环依赖之前,先介绍一下什么是 Bean 的循环依赖,下面通过一个小示例介绍一下 Bean 的循环依赖
setter方式单例
在介绍 Spring 是如何解决循环依赖之前,先介绍一下什么是 Bean 的循环依赖,下面通过一个小示例介绍一下 Bean 的循环依赖
我们分别创建两个类,A 和 B 它们之间相互依赖,如下代码
A 类
@Component
public class A {
private B b;
public A( B b){
this.b = b;
}
public void getB(){
System.out.println(b);
}
}
B 类
@Component
public class B {
private A a;
public B(A a){
this.a = a;
}
public void getA(){
System.out.println(a);
}
}
SpringCircleConfig 类
@Configuration
@ComponentScan("com.zlp.spring.circle.set")
public class SpringCircleConfig {
}
测试类
public class AnnotationSetCircleTest {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringCircleConfig.class);
A a = ac.getBean("a",A.class);
System.out.println(a.toString());
a.getB();
}
}
@Component
public class B {
private A a;
public B(A a){
this.a = a;
}
public void getA(){
System.out.println(a);
}
}
SpringCircleConfig 类
@Configuration
@ComponentScan("com.zlp.spring.circle.set")
public class SpringCircleConfig {
}
测试类
public class AnnotationSetCircleTest {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringCircleConfig.class);
A a = ac.getBean("a",A.class);
System.out.println(a.toString());
a.getB();
}
}
public class AnnotationSetCircleTest {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringCircleConfig.class);
A a = ac.getBean("a",A.class);
System.out.println(a.toString());
a.getB();
}
}
首先回忆一下之前介绍的 Spring 初始化过程的内容,在 Spring 初始化 Bean 的过程中,有一个重要很重要的步骤就是属性赋值。这个属性赋值的过程简单来讲就是获取每一个Bean对象内部的属性,然后通过反射把属性值赋给这个Bean对象。对于基本类型的值,例如String、Integer等,很简单,直接解析赋值即可。
对于对象类型的值,例如上述示例中,它的处理过程是什么呢?
第一步:A对象初始化完毕,此时开始进行属性填充,发现 A中需要注入B,则按照创建Bean的流程去创建B;
第二步:在创建 B的过程中,首先去初始化 B,然后进行属性填充,发现 B 中需要注入 A,则按照创建Bean的流程在去创建A;
按照上述的过程会发现创建带有依赖关系的 Bean 的过程就会变成一个死循环,一直转圈。但是上述的示例可以正常运行,这是因为 Spring 中通过引入了早期对象以及使用多级缓存解决了这个问题。
整体的工作流程如下
早期对象
什么是早期对象呢?
早期对象指的是 Bean 的一个半成品。更确切的说,就是实例化完成(没有初始化),并执行了一部分后置处理器,但是还未填充属性的对象,这个对象不能直接使用。例如对于上例中的 A 对应的早期对象其实就是一个空的 A 实例,如下:
如何使用这个早期对象?
这个早期对象 A 创建出来之后,会被保存在一个地方【Spring的二级缓存中】。在进行B实例的属性赋值过程中,发现依赖了A这个对象,然后就会去找A对象。没找到初始化完成的A对象会再去找这个早期的A对象,找到之后,直接完成B实例的属性赋值,将这个没有初始化完成的A对象赋值给B中的a属性,此时,B完成属性填充。
然后 A 中依赖了 B,A 属性填充过程中同样会将未初始化完成的B对象填充给b属性,至此,完成依赖Bean的属性填充。
使用半成品的对象进行属性填充会有问题吗?
不会有问题。在属性填充过程完成之后,会进行bean的初始化过程,初始化过程中,初始化的 bean对象就是这个半成品对象,因为属性赋值的过程其实就是把对象的引用赋值给依赖Bean的成员变量【反应在内存中就是同一个地址引用】。所以当这个对象被初始化之后,之前已经完成赋值的半成品对象自然就会变成初始化完成的对象。
多级缓存
底层代码是通过三级缓存实现,对应三个Map,代码过于繁多,具体可参考:
DefaultSingletonBeanRegistry.java 类中
private final MapsingletonObjects = new ConcurrentHashMap<>(256); private final Map earlySingletonObjects = new HashMap<>(16); private final Map > singletonFactories = new HashMap<>(16); private final Set registeredSingletons = new linkedHashSet<>(256); protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { // 将创建好的单实例bean放入到单例缓存池中 this.singletonObjects.put(beanName, singletonObject); // 从三级缓存中删除 this.singletonFactories.remove(beanName); // 从二级缓存中删除(早期对象:已经实例化,但是未完成属性赋值的对象) this.earlySingletonObjects.remove(beanName); // 保存到已注册单实例Bean名称集合中 this.registeredSingletons.add(beanName); } } protected void addSingletonFactory(String beanName, ObjectFactory> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { // 如果当前的单实例缓存池中还没有beanName对应的单实例bean if (!this.singletonObjects.containsKey(beanName)) { // 将当前beanName对应的ObjectFactory放入到三级缓存singletonFactories中 this.singletonFactories.put(beanName, singletonFactory); // 从早期的单例对象缓存中移除beanName对应的bean实例 this.earlySingletonObjects.remove(beanName); // 将当前的beanName保存到已经注册的bean对应的Set集合中,标识其已经注册过 this.registeredSingletons.add(beanName); } } } @Override @Nullable public Object getSingleton(String beanName) { // 从缓存中获取单实例Bean return getSingleton(beanName, true); } // ******该段代码是 Spring 解决循环引用的核心代码****** // // 解决循环引用逻辑:使用构造函数创建一个 “不完整” 的 bean 实例(之所以说不完整,是因为此时该 bean 实例还未初始化), // 并且提前曝光该 bean 实例的 ObjectFactory(提前曝光就是将 ObjectFactory 放到 singletonFactories 缓存), // 通过 ObjectFactory 我们可以拿到该 bean 实例的引用,如果出现循环引用,我们可以通过缓存中的 ObjectFactory 来拿到 bean 实例, // 从而避免出现循环引用导致的死循环。 // // 这边通过缓存中的 ObjectFactory 拿到的 bean 实例虽然拿到的是 “不完整” 的 bean 实例,但是由于是单例,所以后续初始化完成后, // 该 bean 实例的引用地址并不会变,所以最终我们看到的还是完整 bean 实例。 // 另外这个代码块中引进了4个重要缓存: // singletonObjects 缓存:beanName -> 单例 bean 对象。 // earlySingletonObjects 缓存:beanName -> 单例 bean 对象,该缓存存放的是早期单例 bean 对象,可以理解成还未进行属性填充、初始化。 // singletonFactories 缓存:beanName -> ObjectFactory。 // singletonsCurrentlyInCreation 缓存:当前正在创建单例 bean 对象的 beanName 集合。 // singletonObjects、earlySingletonObjects、singletonFactories 在这边构成了一个类似于 “三级缓存” 的概念。 @Nullable protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 根据beanName从单实例对象缓存中获取单例对象(singletonObjects为一个ConcurrentHashMap,就是用来保存所有的单实例Bean的, // key:beanName value:beanInstance) 相当于一级缓存 Object singletonObject = this.singletonObjects.get(beanName); // 如果缓存中不存在,而且beanName对应的单实例Bean正在创建中. if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 加锁操作. synchronized (this.singletonObjects) { // 从早期单实例对象缓存中获取单例对象(之所以称为单实例早期对象, // 是因为earlySingletonObjects里面的对象都是通过提前曝光的ObjectFactory创建出来的,还未进行属性的填充) singletonObject = this.earlySingletonObjects.get(beanName); // 如果早期单实例对象缓存中没有,而且允许创建早期单实例对象引用 if (singletonObject == null && allowEarlyReference) { // 则从单例工厂缓存中获取BeanName对应的单例工厂. ObjectFactory> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 如果存在着单例对象工厂,则通过工厂创建一个单例对象, // 调用的是:addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))中的拉姆达表达式 singletonObject = singletonFactory.getObject(); // 将通过单例对象工厂创建的单例对象放入到早期单例对象缓存中,这个早期对象指的是一个空的未完成属性赋值和初始化的对象。 this.earlySingletonObjects.put(beanName, singletonObject); // 移除该beanName对应的单例对象工厂,因为该单例工厂已经创建了一个实例对象,并且放入到earlySingletonObjects缓存中了, // 所以,后续通过beanName获取单例对象,可以通过earlySingletonObjects缓存获取到,不需要再用到该单例工厂. this.singletonFactories.remove(beanName); } } } } return singletonObject; } public Object getSingleton(String beanName, ObjectFactory> singletonFactory) { // 校验bean的名称,不能为空 Assert.notNull(beanName, "Bean name must not be null"); synchronized (this.singletonObjects) { // 首先从单例缓存池singletonObjects【Map 】中尝试获取单实例bean Object singletonObject = this.singletonObjects.get(beanName); // 如果未获取到,则通过如下的过程去创建单实例bean 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 (logger.isDebugEnabled()) { logger.debug("Creating shared instance of singleton bean '" + beanName + "'"); } beforeSingletonCreation(beanName); boolean newSingleton = false; boolean recordSuppressedExceptions = (this.suppressedExceptions == null); if (recordSuppressedExceptions) { this.suppressedExceptions = new linkedHashSet<>(); } try { singletonObject = singletonFactory.getObject(); newSingleton = true; } catch (IllegalStateException ex) { // Has the singleton object implicitly appeared in the meantime -> // if yes, proceed with it since the exception indicates that state. singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { throw ex; } } catch (BeanCreationException ex) { if (recordSuppressedExceptions) { for (Exception suppressedException : this.suppressedExceptions) { ex.addRelatedCause(suppressedException); } } throw ex; } finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; } } }
具体调用方法 AbstractBeanFactory.doGetBean 方法中的 getSingleton(beanName)方法。
三级缓存对象
- singletonObjects:一级缓存,也被称为单实例缓存池,用来缓存所有初始化完成的Bean;
- earlySingletonObjects:二级缓存,用来保存早期对象,这个早期对象就是Bean的一个半成品对象,只完成了实例化化,未进行属性填充和初始化的对象;singletonFactories:三级缓存,用来保存获取早期对象的一个回调函数,通过这个回调函数,可以获取到未初始化完成的早期对象;
查询流程
获取早期对象的实现可参考:
AbstractAutowireCapableBeanFactory.doCreateBean 中的 getEarlyBeanReference 方法。
获取 bean 调用流程方法
构造器参数循环依赖
构造器循环依赖
通过使用早期对象以及多级缓存解决了这种通过set方法注入时存在的循环依赖问题。但是通过构造器注入的方式无法解决,因为通过构造器的方式无法产生早期对象。如下示例:
A类
@Component
public class A {
private B b;
public A(B b){
this.b = b;
}
public void getB(){
System.out.println(b);
}
}
B类
@Component
public class B {
private A a;
public B(A a){
this.a = a;
}
public void getA(){
System.out.println(a);
}
}
测试类不变,运行之后,会提示如下异常:
BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
解决方式
构造器方式注入产生的循环依赖,可以通过指定 Bean 的延迟加载,先注入代理对象,然后在需要使用这个bean的时候再去真实创建,如下:
A类
@Component
public class A {
private B b;
public A(@Lazy B b){
this.b = b;
}
public void getB(){
System.out.println(b);
}
}
B类
@Component
public class B {
private A a;
public B(@Lazy A a){
this.a = a;
}
public void getA(){
System.out.println(a);
}
}
通过其他例如 @PostConstruct,或者使用后置处理器的方式也可以解决,但是如果真有这种循环依赖问题,建议还是使用最简单,也最容易理解的set方式去注入,因为这种方式框架本身就已经解决了这种问题。
setter方式原型,prototype
我们在A类和B类中,添加 @Scope(value = "prototype") 注解
@Component
@Scope(value = "prototype")
public class A {
@Autowired
private B b;
public void getB(){
System.out.println(b);
}
}
scope="prototype" 意思是 每次请求都会创建一个实例对象。两者的区别是:有状态的 bean 都使用Prototype作用域,无状态的一般都使用singleton单例作用域。
测试从新启动报错
Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a';
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
为什么原型模式就报错了呢 ?
对于“prototype”作用域Bean,Spring 容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。
几个 QA? 三级缓存放流程
QA 问题
1. 三级缓存存对象,在获取数据的时候什么顺序来获取的
先获取一级缓存,没有在获取二级缓存,没有再获取三级缓存,所以当前面的缓存存在对象先获取。
2. 一级能解决循环依赖问题
有一级缓存,那么成品对象和半成品对象会放到一起,这个是没办法区分了,所以需要两个缓存来分别存放不同状态的对象,一级缓存放成品,二级缓存放半成品。
3. 如果只有二个缓存,能否解决循环依赖问题?
在刚刚的整个流程中,三级缓存一共出现了几次? getsingleton, doCreateBean 如果对象的创建过程中不包含 Aop,那么二级缓存就可以解决循环依赖问题,但是包含 Aop 的操作,循环依赖问题是解决不了的。
4. 为什么添加了 Aop 的操作之后就需要添加三级缓存来解决这个问题?三级缓存加了什么操作?
添加了一个 getEarlyBeanReference 的方法在创建代理对象的时候,
是否需要生成原始对象?
需要当创建完成原始对象之后,后续有霃要创建代理对象,那么对象在引用的时候应该使用哪一个换句话说,就是一个 beanName对应有两个对象,(原始对象和代理对象在整个容器中,有且仅能有一个同名的对象,当需要生成代理对象的时候,就要把代理对象覆盖原
程序是怎么知道在什么时候要进行代理对象的创建的呢?
需要一个类似于回调的接口判断,当需要第一次对外暴露使用的时候,来判断当前对象是否需要去创建代理对象,getEarlyBeanReference 方法的判断,如果需要代理就返回代理对象,如果没有代理就返回原始对象。
参数文档
- Spring循环依赖的三种方式
- Spring如何解决bean的循环依赖?



