Springboot循环依赖实践纪实

测试的Springboot版本: 2.6.4,禁止了循环依赖,但是可以通过 application.yml 开启(哈哈)

@Lazy注解解决循环依赖

情况一:只有简单属性关系的循环依赖

涉及的Bean:

  • ASerivce 及其实现类 ASerivceImpl
  • BSerivce 及其实现类 BSerivceImpl

com.example.demo.service.AService

package com.example.demo.service;


public interface AService {
    void zaWaLuDo();
}

com.example.demo.service.impl.AServiceImpl

package com.example.demo.service.impl;

import com.example.demo.service.AService;
import com.example.demo.service.BService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AServiceImpl implements AService {

    @Autowired
    public BService bService;

    @Override
    public void zaWaLuDo(){
        System.out.println("ToKiOToMaLei!");
    }
}

com.example.demo.service.BService

package com.example.demo.service;

public interface BService {

}

com.example.demo.service.impl.BServiceImpl

package com.example.demo.service.impl;

import com.example.demo.service.AService;
import com.example.demo.service.BService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BServiceImpl implements BService {
    @Autowired
    public AService aService;
}

此时 ASerivce 和 BService 构成循环依赖的关系:

测试类: com.example.demo.service.AServiceTest

package com.example.demo.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class AServiceTest {
    @Autowired
    AService aService;

    @Test
    public void test(){
        aService.zaWaLuDo();
    }

}

此时运行 test 方法,将会报错:

java.lang.IllegalStateException: Failed to load ApplicationContext

	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:132)
	at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:118)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
	at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:43)
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:248)
	at org.springframework.test.context.junit.jupiter.SpringExtension.postProcessTestInstance(SpringExtension.java:138)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$8(ClassBasedTestDescriptor.java:363)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.executeAndMaskThrowable(ClassBasedTestDescriptor.java:368)
	at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeTestInstancePostProcessors$9(ClassBasedTestDescriptor.java:363)
......省略.....
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'AServiceImpl': Unsatisfied dependency expressed through field 'bService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'BServiceImpl': Unsatisfied dependency expressed through field 'aService'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AServiceImpl': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.resolveFieldValue(AutowiredAnnotationBeanPostProcessor.java:659)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:639)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)

最重要的一句应该是:

美观处理过:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: 
	Error creating bean with name 'AServiceImpl': Unsatisfied dependency expressed through field 'bService'; 
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: 
	Error creating bean with name 'BServiceImpl': Unsatisfied dependency expressed through field 'aService'; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: 
	Error creating bean with name 'AServiceImpl': Requested bean is currently in creation: 
		Is there an unresolvable circular reference?

Spring提醒我们可能存在 circular reference ,就是大名鼎鼎的循环依赖。

解决办法

在其中任意一个属性注入 @Autowired 上加入懒加载 @Lazy 即可跑通,比如在 AService 的实现类中加入:

package com.example.demo.service.impl;

import com.example.demo.service.AService;
import com.example.demo.service.BService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

@Service
public class AServiceImpl implements AService {

    @Autowired
    @Lazy //懒加载
    public BService bService;

    @Override
    public void zaWaLuDo(){
        System.out.println("ToKiOToMaLei!");
    }
}

此时,运行测试方法 test() 的运行结果就是:

ToKiOToMaLei!

说明 aService.zaWaLuDo() 方法执行成功

源码分析

参考: https://www.zhihu.com/question/438247718

主要是靠Spring中(人为定义)的三级缓存有关:

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

第一级缓存: **Map<String, Object> singletonObjects**
第一级缓存的作用?

  • 用于存储单例模式下创建的Bean实例(已经创建完毕)。
  • 该缓存是对外使用的,指的就是使用Spring框架的程序员。

存储什么数据?

  • K:bean的名称
  • V:bean的 实例对象, 或者说:“成品”对象 (有代理对象则指的是代理对象,已经创建完毕)

第二级缓存: **Map<String, Object> earlySingletonObjects**
第二级缓存的作用?

  • 用于存储单例模式下创建的Bean实例(该Bean被提前暴露的引用,该Bean还在创建中)。
  • 该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存。

存储的数据:

  • K:bean的名称
  • V:bean的实例对象,“半成品”对象(有代理对象则指的是代理对象,该Bean还在创建中)

第三级缓存: **Map<String, ObjectFactory<?>> singletonFactories**
第三级缓存的作用?

  • 通过ObjectFactory对象来存储单例模式下提前暴露的Bean实例的引用(正在创建中)。
  • 该缓存是对内使用的,指的就是Spring框架内部逻辑使用该缓存。
  • 此缓存是解决循环依赖最大的功臣

存储什么数据?

  • K:bean的名称
  • V:ObjectFactory,该对象持有提前暴露的bean的引用

一、注入 AService 时,首先进入 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean :

// Create bean instance.
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
                        ...
                        }

看看 getSingleton 方法的原型, org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>) :

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)

所以此时 doGetBean 方法会进入lambda方法中的,调用 createBean 方法来得到一个 ObjectFactory
接着我们进入到 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory 的 doCreateBean 方法, 打上断点看看:

  1. 当 beanName='AServiceImpl' 的时候,先根据反射创建了一个 Object 类的 AServiceImpl 的bean,里面的 BService 为 null :
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
 	...省略...
        Object bean = instanceWrapper.getWrappedInstance(); //ASericeImpl@4686
    	Class<?> beanType = instanceWrapper.getWrappedClass(); //beanType = "class com.example.demo.service.impl.AServiceImpl"
    ...省略...
}
  1. 判断该bean是否已经被提前暴露
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
    	...省略...
        
        //判断该bean是否已经被提前暴露
       	//Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
    	//如果是,就调用addSingletonFactory方法,
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}
   	 ...省略...
}
  1. 若没有被提前暴露,就进入到语句:
// Initialize the bean instance.
		Object exposedObject = bean;
		try {
            //调用populateBean方法后,AService中的BService属性就不再是null,而是一个$Proxy@4981$,
            //应该是个代理的对象,解决注入的燃眉之急
			populateBean(beanName, mbd, instanceWrapper);
            //做一些初始化的操作
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
  1. 将该bean暴露
// Register bean as disposable.
		try {
			registerDisposableBeanIfNecessary(beanName, bean, mbd);
		}
  1. 接着就将其返回
return exposedObject;

此时, exposedObject 对象里的 bService 还是 $Proxy$

二、回到 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 方法:

if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
                            //这时候回到了这里,lambda表达式会得到上面的exposedObject
							return createBean(beanName, mbd, args);
						}

此时会回到 getSingleton 方法中,进入 getSingleton 方法内部:

try {
                    //其中的singletonFactory调用getObject就是lambda表达式返回的exposedObject,也就是里面的bService还是$Proxy$
					singletonObject = singletonFactory.getObject();
					//标记为新的单例bean
                    newSingleton = true;
				}

最后我们看看, this.singletonObjects 中的 AService :

可以看到用 bService 中注入了一个神秘的 $Proxy$ ,然后写入了一级缓存中,经调试后发现是在 getSingleton 方法中,调用 addSingleton 方法写入的,这时候二级、三级缓存全程都没写入过数据。

if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}

三、回到 test() 方法:

@Test
    public void test(){
        aService.zaWaLuDo();
    }

此时, aService 中的 bService 还是个 &Proxy$
这时候继续就会正常执行 aService.zaWaLuDo_()_ ,程序正常结束。

总结下就是这种情况下, aService 会由 doCreateBean 方法创建,而 bService 是某种代理的东西存在其中。

四、我们改写一下两个Service,使 AService 需要调用 BService 的方法:

  1. AServiceImpl
package com.example.demo.service.impl;

import com.example.demo.service.AService;
import com.example.demo.service.BService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

@Service
public class AServiceImpl implements AService {

    @Autowired
    @Lazy
    public BService bService;

    @Override
    public void zaWaLuDo(){
        System.out.println("ToKiOToMaLei!");
        bService.starPuLaXin();
    }
}
  1. BServiceImpl
package com.example.demo.service.impl;

import com.example.demo.service.AService;
import com.example.demo.service.BService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BServiceImpl implements BService {
    @Autowired
    public AService aService;

    @Override
    public void starPuLaXin() {
        System.out.println("Za WaLuDo!");
    }
}

我们先在执行 aServuce,zaWaLuDo() 之前打个断点看看此时的 aService 是什么情况:

可以看到跟上面的情况是一样的。

这个时候我们在 org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String) 方法打个断点看看,这时候会进入 org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 方法,执行:

// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);

sharedInstance 是这样的:

里面的 bService 还是一个 $Proxy$ ,我们一直调试到 getBean 方法结束,他最终会进入到jdk代理 org.springframework.aop.framework.JdkDynamicAopProxy 中执行方法:

//方法参数
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
//target根据参数argsToUse执行方法method的结果
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);

于是就会执行结果:

ToKiOToMaLei!
Za WaLuDo!

五、研究一下 aService 和 bService 的注入过程,二者都会进入 doCreateBean 方法, aService 会入上面的过程一样被创建,我们研究一下 bService 的创建过程,当执行到:

try {
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}

执行完 populateBean 方法, exposedObject (即将成型的 bService )就被注入了 aService :

但是这个 aService 中的 bService 实际上还只是个 $Proxy$ ,在接下来的过程中, aService 中的 bService 一直都还只是个 $Proxy$ ,就像 bService 中的 aService 也一直都还是个 $Proxy$ ,所以可以推断,这种情况下Spring不关心二者是否真的存了个“成品”对象,只是有个“半成品”对象利用通过jdk动态代理AOP执行 bService 的方法而已, 最终会到: org.springframework.aop.framework.JdkDynamicAopProxy#invoke 中执行 bService 的方法.

情况二:构造器注入循环依赖示例

  • com.example.demo.service.CService
package com.example.demo.service;

public interface CService {
    void goldExperience();
}
  • com.example.demo.service.DService
package com.example.demo.service;

public interface DService {
}
  • com.example.demo.service.impl.CServiceImpl
package com.example.demo.service.impl;

import com.example.demo.service.CService;
import com.example.demo.service.DService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CServiceImpl implements CService {

    private DService dService;

    @Autowired
    public CServiceImpl(DService dService) {
        this.dService = dService;
    }

    @Override
    public void goldExperience() {
        System.out.println("MUDAMUDAMUDAMUDA!!!!");
    }
}
  • com.example.demo.service.impl.DServiceImpl
package com.example.demo.service.impl;

import com.example.demo.service.CService;
import com.example.demo.service.DService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DServiceImpl implements DService {
    private CService cService;

    @Autowired
    public DServiceImpl(CService cService) {
        this.cService = cService;
    }
}
  • com.example.demo.service.CServiceTest
package com.example.demo.service;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class CServiceTest {
    @Autowired
    CService cService;

    @Test
    public void test(){
        cService.goldExperience();
    }
}

运行测试方法,同样报循环依赖的错误。

解决方法

在参数里添加 @Lazy 方法:

package com.example.demo.service.impl;

import com.example.demo.service.CService;
import com.example.demo.service.DService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

@Service
public class CServiceImpl implements CService {

    private DService dService;

    @Autowired
    public CServiceImpl(@Lazy DService dService) { //参数上添加了@Lazy方法
        this.dService = dService;
    }

    @Override
    public void goldExperience() {
        System.out.println("MUDAMUDAMUDAMUDA!!!!");
    }
}

源码分析

跟情况一一样,也是通过注入一个"假"的对象解决:

将代码改成情况一的调用 dService 的方法也是通过jdk动态代理AOP解决。

Springboot解决循环依赖的源码阅读

在 application.yml 中开启:

spring:
  main:
    allow-circular-references: true

我们先调试一下看看:

发现 cService 中的 dService 是货真价实的

我们尝试调试看看能不能搞清楚Springboot到底是怎么注入的,在老地方 org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String) 打个断点,研究一下 dService 是怎么做的:

@Override
	public Object getBean(String name) throws BeansException {
		return doGetBean(name, null, null, false);
	}

一直调试到 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor 才终于有点眉目:

if (value != null) {
				ReflectionUtils.makeAccessible(field);
				field.set(bean, value);
			}

此时的各变量是这样的:

field.set(bean, value) 方法内部是 getFieldAccessor_(_obj_)_.set_(_obj, value_)_; 后面的 set_(_obj, value_)_ 就是已编译好的字节码了,执行下一步后, dService 中缺乏的 cService 就有东西了,所以可以推测是一个写入过程。

我们看看其巨长无比的调用栈:

在这句所在的 inject 方法头部打个注解,看看有没有头绪, cService 是从哪来的,重新启动一下

当创建 dService 时,会进入到该方法体,初始的时候 value 啥也没有,接着会进到:

...
	value = resolveFieldValue(field, bean, beanName); //进入本句
...
    if (value != null) {
        ReflectionUtils.makeAccessible(field);
        field.set(bean, value);
    }

一直调用到 org.springframework.beans.factory.config.DependencyDescriptor#resolveCandidate 方法:

public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
			throws BeansException {

		return beanFactory.getBean(beanName); //beanName="CServiceImpl"
	}

此时由进入到了老朋友 org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String) 方法中,返回了一个 CServiceImpl 对象给上面的 resolveFieldValue(field, bean, beanName); 接着就进入到 field.set(bean, value); 中将其注入,那么神奇的事情肯定是发生在 beanFactory.getBean(beanName); 中

老办法,再打个断点,回到取 CServiceImpl 对象的时候看看:

接着他会进入老朋友 org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String) 方法中,我们step into,在执行完 Object sharedInstance = getSingleton(beanName) 后就有了 CServiceImpl 对象,只不过他的 dService 还是 null :

protected <T> T doGetBean(
			String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
			throws BeansException {

		String beanName = transformedBeanName(name);
		Object beanInstance;

		// Eagerly check singleton cache for manually registered singletons.
		Object sharedInstance = getSingleton(beanName);
    ....

最后还是会 field.set(bean, value); 给 dService 先注入。

看到这里感觉非常混乱,感觉还是按那幅图来看吧:

  1. 在创建 cService 调用 doCreateBean 方法,执行了 addSingletonFactory :
{
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}

三级缓存 this.singletonFactories 中便存入了“半成品”对象的自己:

  1. cService 执行到 populateBean 的时候,旋即进入到了 dService 的 doCreateBean
  2. dService 通过 addSingletonFactory 也往三级缓存 this.singletonFactories 中便存入了“半成品”对象的自己,此时c、d都在三级缓存 this.singletonFactories 里:
  3. 当 dService 执行了下一句,即 populateBean 之后, cService 从三级缓存换入到了二级缓存 this.earlySingletonObjects :
    此时其内部的 dService 为空:
  4. 到这一步,我们再理清一下调用链:

    dService 执行到 getSingleton 后:
// Create bean instance.
if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
        try {
            return createBean(beanName, mbd, args);
        }

在 getSingleton 内部执行了 addSingleton_(_beanName, singletonObject_)_ 之后,便把自己写入了三级缓存 this.singletonObjects 中,并把半成品的 cService 注入到自己中,形如:

  1. 之后就回到了 cService -> populateBean 的执行,最终去到了 field.set_(_bean, value_)_ 中,此时bean为 cService , value为 dService (内部的 cService 的 dService 仍未空),执行完之后,就链接上了!神奇!:
  2. 待 cService ->populateBean 执行结束后,回到 cService -> doGetBean 的执行,进行完 cService -> getSingleton 后,二级缓存 this.earlySingletonObjects 中的 cService 也移入了一级缓存 this.singletonObjects 之中: 此时,基本上解决了循环引用的问题。

总结一下

  1. 我们认为一个对象的创建可分为两步:
    1. 实例化:可认为是一个在内存中划分空间的过程
    2. 初始化:为该对象的属性赋值的过程
  2. cService 在创建的时候,先只进行了实例化,此时是一个“半成品”对象,写入三级缓存中存储;
  3. 旋即进行其所需要的 dService 对象的创建,而 dService 会进行实例化之后,也将“半成品”的自己写入三级缓存中,
  4. 此时 cService 会尝试进行初始化(为属性赋值),他需要写入 dService ,但事实上会为 dService 赋值为null,然后写入二级缓存,此时的 cService 仍然是个“半成品”。
  5. 接着又回到 dService 的创建,这时候他也会进行一个初始化,将二级队列中的完成了“假”的初始化的“半成品”对象 cService ,给自己所需的属性注入,完成了初始化过程,并写入了一级缓存。
  6. 然后就回到了 cService 还在进行中的创建过程,这个时候 cService 是个“假”的“半成品”,在二级缓存,而 dService 是个真的成品,他确实拥有了 cService 对象。
  7. cService 这时候也会将一级缓存中的 dService ,一个真正完成了初始化的对象,注入到自己的属性中,这个时候二者终于完成了互相注入, cService 也完成了初始化,进入了一级缓存,循环依赖得以解决。

为什么需要三级缓存?

我大概总结了一下流程:

可以看出,互相依赖的两个对象有三种状态:

  1. 只有“存在”,没有内部的“半成品”形态一对象
  2. 注入了“半成品”形态一对象的“半成品”形态二对象
  3. 注入了“半成品”形态二对象的完全体“成品”对象

因有客观上有三种状态的对象,所以才利用三级缓存来分别存储,比较科学的说明如下:

三级缓存

singletonObjects

一级缓存, Cache of singleton objects bean name --> bean instance。 存放完整对象。

earlySingletonObjects

二级缓存, Cache of early singleton objects bean name --> bean instance 提前曝光的BEAN缓存。 存放半成品对象。

singletonFactories

三级缓存, Cache of singleton factories bean name --> ObjectFactory。需要的对象被代理时,就必须使用三级缓存(否则二级就够了)。解决循环依赖中存在aop的问题 存放 lambda 表达式和对象名称的映射。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值