原文連結
關注“蘇三說技術”,回複:開發手冊、時間管理 有驚喜。
也許有些朋友對spring的循環依賴問題并不了解,讓我們先一起看看這個例子。
@Service
public class AService {
private BService bService;
public AService(BService bService) {
this.bService = bService;
}
public void doA() {
System.out.println("call doA");
}
}
@Service
public class BService {
private AService aService;
public BService(AService aService) {
this.aService = aService;
}
public void doB() {
System.out.println("call doB");
}
}
@RequestMapping("/test")
@RestController
public class TestController {
@Autowired
private AService aService;
@RequestMapping("/doSameThing")
public String doSameThing() {
aService.doA();
return "success";
}
}
@SpringBootApplication
public class Application {
/**
* 程式入口
* @param args 程式輸入參數
*/
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(WebApplicationType.SERVLET).run(args);
}
}
我們在運作Application類的main方法啟動服務時,報了如下異常:
Requested bean is currently in creation: Is there an unresolvable circular reference?

這裡提示得很明顯,出現了循環依賴。
什麼是循環依賴?
循環依賴是執行個體a依賴于執行個體b,執行個體b又依賴于執行個體a。
或者執行個體a依賴于執行個體b,執行個體b依賴于執行個體c,執行個體c又依賴于執行個體a。
像這種多個執行個體之間的互相依賴關系構成一個環形,就是循環依賴。
為什麼會形成循環依賴?
上面的例子中AService執行個體化時會調用構造方法 public AService(BService bService),該構造方法依賴于BService的執行個體。此時BService還沒有執行個體化,需要調用構造方法public BService(AService aService)才能完成執行個體化,該構造方法巧合又需要AService的執行個體作為參數。由于AService和BService都沒有提前執行個體化,在執行個體化過程中又互相依賴對方的執行個體作為參數,這樣構成了一個死循環,是以最終都無法再執行個體化了。
spring要如何解決循環依賴?
隻需要将上面的例子稍微調整一下,不用構造函數注入,直接使用Autowired注入。
@Service
public class AService {
@Autowired
private BService bService;
public AService() {
}
public void doA() {
System.out.println("call doA");
}
}
@Service
public class BService {
@Autowired
private AService aService;
public BService() {
}
public void doB() {
System.out.println("call doB");
}
}
我們看到可以正常啟動了,說明循環依賴被自己解決了
spring為什麼能循環依賴?
調用applicationContext.getBean(xx)方法,最終會調到AbstractBeanFactory類的doGetBean方法。由于該方法很長,我把部分不相幹的代碼省略掉了。
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean;
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
省略........
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
省略........
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; consider " +
"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
省略........
return (T) bean;
}
我們可以看到,該方法一進來會調用getSingleton方法從緩存擷取執行個體,如果擷取不到。會判斷作用域是否為:單例,多列 或者 都不是,不同的作用域建立執行個體的規則不一樣。接下來,我們重點看一下getSingleton方法。
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
我們發現有三個Map集合:
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
singletonObjects對應一級緩存,earlySingletonObjects對應二級緩存,singletonFactories對應三級緩存。
上面getSingleton方法的邏輯是:
- 先從singletonObjects(一級緩存)中擷取執行個體,如果可以擷取到則直接傳回singletonObject執行個體。
- 如果從singletonObjects(一級緩存)中擷取不對執行個體,再從earlySingletonObjects(二級緩存)中擷取執行個體,如果可以擷取到則直接傳回singletonObject執行個體。
- 如果從earlySingletonObjects(二級緩存)中擷取不對執行個體,則從singletonFactories(三級緩存)中擷取singletonFactory,如果擷取到則調用getObject方法建立執行個體,把建立好的執行個體放到earlySingletonObjects(二級緩存)中,并且從singletonFactories(三級緩存)删除singletonFactory執行個體,然後傳回singletonObject執行個體。
- 如果從singletonObjects、earlySingletonObjects和singletonFactories中都擷取不到執行個體,則singletonObject對象為空。
擷取執行個體需要調用applicationContext.getBean("xxx")方法,第一次調用getBean方法,代碼走到getSingleton方法時傳回的singletonObject對象是空的。然後接着往下執行,預設情況下bean的作用域是單例的,接下來我們重點看看這段代碼:
createBean方法會調用doCreateBean方法,該方法同樣比較長,我們把不相幹的代碼省略掉。
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
BeanWrapper instanceWrapper = null;
省略......
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper.getWrappedInstance();
省略........
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
省略 .....
}
省略 .......
return exposedObject;
}
該方法的主要流程是:
- 建立bean執行個體
- 判斷作用域是否為單例,允許循環依賴,并且目前bean正在建立,還沒有建立完成。如果都滿足條件,則調用addSingletonFactory将bean執行個體放入緩存中。
- 調用populateBean方法進行依賴注入
- 調用initializeBean方法完成對象初始化和AOP增強
我們關注的重點可以先放到addSingletonFactory方法上。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
該方法的邏輯是判斷如果singletonObjects(一級緩存)中找不到執行個體,則将singletonFactory執行個體放到singletonFactories(三級緩存)中,并且移除earlySingletonObjects(二級緩存)中的執行個體。
createBean方法執行完之後,會調用外層的getSingleton方法
我們重點看看這個getSingleton方法
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!)");
}
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) {
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;
}
}
該方法邏輯很簡單,就是先從singletonObjects(一級緩存)中擷取執行個體,如果擷取不到,則調用singletonFactory.getObject()方法建立一個執行個體,然後調用addSingleton方法放入singletonObjects緩存中。
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
該方法會将執行個體放入singletonObjects(一級緩存),并且删除singletonFactories(二級緩存),這樣以後再調用getBean時,都能從singletonObjects(一級緩存)中擷取到執行個體了。
說了這麼多,再回到示例中的場景。
spring為什麼要用三級緩存,而不是二級緩存?
像示例的這種情況隻用二級緩存是沒有問題的。
但是假如有這種情況:a執行個體同時依賴于b執行個體和c執行個體,b執行個體又依賴于a執行個體,c執行個體也依賴于a執行個體。
a執行個體化時,先提前暴露objectFactorya到三級緩存,調用getBean(b)依賴注入b執行個體。b執行個體化之後,提前暴露objectFactoryb到三級緩存,調用getBean(a)依賴注入a執行個體,由于提前暴露了objectFactorya,此時可以從三級緩存中擷取到a執行個體, b執行個體完成了依賴注入,更新為一級緩存。a執行個體化再getBean(c)依賴注入c執行個體,c執行個體化之後,提前暴露objectFactoryc到三級緩存,調用getBean(a)依賴注入a執行個體,由于提前暴露了objectFactorya,此時可以從二級緩存中擷取到a執行個體。注意這裡又要從二(原文說的是三,我覺得它寫錯了)級緩存中擷取a執行個體,我們知道三級緩存中的執行個體是通過調用singletonFactory.getObject()方法擷取的,傳回結果每次都可能不一樣。如果不用二(原文說的是三,我覺得它寫錯了)級緩存,這裡會有問題,兩次擷取的a執行個體不一樣。
總結:
隻有單例的情況下才能解決循環依賴問題,并且allowCircularReferences要設定成true。
以下情況還是會出現循環依賴:
- 構造器注入
- 作用域非單例的情況,當然在自定義作用域,自己可以實作避免循環依賴的邏輯
- allowCircularReferences參數設定為false
最後說一句(求關注,别白嫖我)
如果這篇文章對您有所幫助,或者有所啟發的話,幫忙關注一下,您的支援是我堅持寫作最大的動力。
求一鍵三連:點贊、轉發、在看。
此外關注公衆号:【蘇三說技術】,在公衆号中回複:面試、代碼神器、開發手冊、時間管理有超贊的粉絲福利,另外回複:加群,可以跟很多BAT大廠的前輩交流和學習。