測試的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()
方法執行成功
源碼分析
主要是靠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
方法, 打上斷點看看:
- 當
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"
...省略...
}
- 判斷該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));
}
...省略...
}
- 若沒有被提前暴露,就進入到語句:
// Initialize the bean instance.
Object exposedObject = bean;
try {
//調用populateBean方法后,AService中的BService屬性就不再是null,而是一個$Proxy@4981$,
//應該是個代理的對象,解決注入的燃眉之急
populateBean(beanName, mbd, instanceWrapper);
//做一些初始化的操作
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
- 將該bean暴露
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
- 接着就將其返回
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
的方法:
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();
}
}
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
先注入。
看到這里感覺非常混亂,感覺還是按那幅圖來看吧:
- 在創建
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
中便存入了“半成品”對象的自己:
cService
執行到populateBean
的時候,旋即進入到了dService
的doCreateBean
dService
通過addSingletonFactory
也往三級緩存this.singletonFactories
中便存入了“半成品”對象的自己,此時c、d都在三級緩存this.singletonFactories
里:
- 當
dService
執行了下一句,即populateBean
之后,cService
從三級緩存換入到了二級緩存this.earlySingletonObjects
:
此時其內部的dService
為空:
- 到這一步,我們再理清一下調用鏈:
dService
執行到getSingleton
后:
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
在getSingleton
內部執行了addSingleton_(_beanName, singletonObject_)_
之后,便把自己寫入了三級緩存this.singletonObjects
中,並把半成品的cService
注入到自己中,形如:
- 之后就回到了
cService
->populateBean
的執行,最終去到了field.set_(_bean, value_)_
中,此時bean為cService
, value為dService
(內部的cService
的dService
仍未空),執行完之后,就鏈接上了!神奇!: - 待
cService ->populateBean
執行結束后,回到cService -> doGetBean
的執行,進行完cService -> getSingleton
后,二級緩存this.earlySingletonObjects
中的cService
也移入了一級緩存this.singletonObjects
之中:
此時,基本上解決了循環引用的問題。
總結一下
- 我們認為一個對象的創建可分為兩步:
- 實例化:可認為是一個在內存中划分空間的過程
- 初始化:為該對象的屬性賦值的過程
cService
在創建的時候,先只進行了實例化,此時是一個“半成品”對象,寫入三級緩存中存儲;- 旋即進行其所需要的
dService
對象的創建,而dService
會進行實例化之后,也將“半成品”的自己寫入三級緩存中, - 此時
cService
會嘗試進行初始化(為屬性賦值),他需要寫入dService
,但事實上會為dService
賦值為null,然后寫入二級緩存,此時的cService
仍然是個“半成品”。 - 接着又回到
dService
的創建,這時候他也會進行一個初始化,將二級隊列中的完成了“假”的初始化的“半成品”對象cService
,給自己所需的屬性注入,完成了初始化過程,並寫入了一級緩存。 - 然后就回到了
cService
還在進行中的創建過程,這個時候cService
是個“假”的“半成品”,在二級緩存,而dService
是個真的成品,他確實擁有了cService
對象。 cService
這時候也會將一級緩存中的dService
,一個真正完成了初始化的對象,注入到自己的屬性中,這個時候二者終於完成了互相注入,cService
也完成了初始化,進入了一級緩存,循環依賴得以解決。
為什么需要三級緩存?
我大概總結了一下流程:
可以看出,互相依賴的兩個對象有三種狀態:
- 只有“存在”,沒有內部的“半成品”形態一對象
- 注入了“半成品”形態一對象的“半成品”形態二對象
- 注入了“半成品”形態二對象的完全體“成品”對象
因有客觀上有三種狀態的對象,所以才利用三級緩存來分別存儲,比較科學的說明如下:
三級緩存
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 表達式和對象名稱的映射。
參考
- https://www.zhihu.com/question/438247718
- https://blog.csdn.net/qq_18298439/article/details/88818418
- https://www.zhihu.com/question/438247718/answer/2331910821
本文僅為分享學習經歷,歡迎批評指正