掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流

10年積累的成都網(wǎng)站建設(shè)、做網(wǎng)站經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問(wèn)題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先建設(shè)網(wǎng)站后付款的網(wǎng)站建設(shè)流程,更有綏中免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
一般場(chǎng)景是一個(gè)Bean A依賴Bean B,而Bean B也依賴Bean A :Bean A → Bean B → Bean A當(dāng)然我們也可以添加更多的依賴層次,比如:Bean A → Bean B → Bean C → Bean D → Bean E → Bean A
當(dāng)Spring上下文在加載所有的bean時(shí),他會(huì)嘗試按照他們他們關(guān)聯(lián)關(guān)系的順序進(jìn)行創(chuàng)建。比如,如果不存在循環(huán)依賴時(shí),例如:Bean A → Bean B → Bean CSpring會(huì)先創(chuàng)建Bean C,再創(chuàng)建Bean B(并將Bean C注入到Bean B中),最后再創(chuàng)建Bean A(并將Bean B注入到Bean A中)。但是,如果我們存在循環(huán)依賴,Spring上下文不知道應(yīng)該先創(chuàng)建哪個(gè)Bean,因?yàn)樗鼈円蕾囉诒舜恕T谶@種情況下,Spring會(huì)在加載上下文時(shí),拋出一個(gè)BeanCurrentlyInCreationException。當(dāng)我們使用構(gòu)造方法進(jìn)行注入時(shí),就會(huì)遇到這種情況。如果您使用其它類型的注入,你應(yīng)該不會(huì)遇到這個(gè)問(wèn)題。因?yàn)樗窃谛枰獣r(shí)才會(huì)被注入,而不是上下文加載被要求注入。
我們定義兩個(gè)Bean并且互相依賴(通過(guò)構(gòu)造函數(shù)注入)。
package com.test.demo.service;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class A {
private B b;
public A (B b) {
this.b = b;
}
}package com.test.demo.service;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class B {
private A a ;
public B(A a) {
this.a = a;
}
}這個(gè)時(shí)候我們我們啟動(dòng)項(xiàng)目就會(huì)發(fā)現(xiàn)項(xiàng)目啟動(dòng)不起來(lái),如下提示信息
/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:/private/var/folders/03/_9q6fw895913yznz6wq544_00000gn/T/AppTranslocation/332DF0A0-AF1B-49F1-8739-1BF4A9F4BAF7/d/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=50456:/private/var/folders/03/_9q6fw895913yznz6wq544_00000gn/T/AppTranslocation/332DF0A0-AF1B-49F1-8739-1BF4A9F4BAF7/d/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/tools.jar:/Users/haolonglong/IdeaProjects/project_boot/target/classes:/Users/haolonglong/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.4.0-SNAPSHOT/spring-boot-starter-web-2.4.0-20200709.121446-178.jar:/Users/haolonglong/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.4.0-SNAPSHOT/spring-boot-starter-json-2.4.0-20200709.121446-178.jar:/Users/haolonglong/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.11.1/jackson-databind-2.11.1.jar:/Users/haolonglong/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.11.1/jackson-annotations-2.11.1.jar:/Users/haolonglong/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.11.1/jackson-core-2.11.1.jar:/Users/haolonglong/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.1/jackson-datatype-jdk8-2.11.1.jar:/Users/haolonglong/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.1/jackson-datatype-jsr310-2.11.1.jar:/Users/haolonglong/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.11.1/jackson-module-parameter-names-2.11.1.jar:/Users/haolonglong/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/2.4.0-SNAPSHOT/spring-boot-starter-tomcat-2.4.0-20200709.121446-178.jar:/Users/haolonglong/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.36/tomcat-embed-core-9.0.36.jar:/Users/haolonglong/.m2/repository/org/glassfish/jakarta.el/3.0.3/jakarta.el-3.0.3.jar:/Users/haolonglong/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.36/tomcat-embed-websocket-9.0.36.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-web/5.3.0-M1/spring-web-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-beans/5.3.0-M1/spring-beans-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-webmvc/5.3.0-M1/spring-webmvc-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-aop/5.3.0-M1/spring-aop-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-context/5.3.0-M1/spring-context-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-expression/5.3.0-M1/spring-expression-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/springframework/boot/spring-boot-starter/2.4.0-SNAPSHOT/spring-boot-starter-2.4.0-20200709.121446-178.jar:/Users/haolonglong/.m2/repository/org/springframework/boot/spring-boot/2.4.0-SNAPSHOT/spring-boot-2.4.0-20200709.121446-178.jar:/Users/haolonglong/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.4.0-SNAPSHOT/spring-boot-autoconfigure-2.4.0-20200709.121446-178.jar:/Users/haolonglong/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.4.0-SNAPSHOT/spring-boot-starter-logging-2.4.0-20200709.121446-178.jar:/Users/haolonglong/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:/Users/haolonglong/.m2/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:/Users/haolonglong/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.13.3/log4j-to-slf4j-2.13.3.jar:/Users/haolonglong/.m2/repository/org/apache/logging/log4j/log4j-api/2.13.3/log4j-api-2.13.3.jar:/Users/haolonglong/.m2/repository/org/slf4j/jul-to-slf4j/1.7.30/jul-to-slf4j-1.7.30.jar:/Users/haolonglong/.m2/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-core/5.3.0-M1/spring-core-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-jcl/5.3.0-M1/spring-jcl-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/yaml/snakeyaml/1.26/snakeyaml-1.26.jar:/Users/haolonglong/.m2/repository/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar com.test.demo.DemoApplication
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.0-SNAPSHOT)
2020-07-10 08:01:20.125 INFO 879 --- [ main] com.test.demo.DemoApplication : Starting DemoApplication using Java 1.8.0_211 on localhost with PID 879 (/Users/haolonglong/IdeaProjects/project_boot/target/classes started by haolonglong in /Users/haolonglong/IdeaProjects/project_boot)
2020-07-10 08:01:20.128 INFO 879 --- [ main] com.test.demo.DemoApplication : No active profile set, falling back to default profiles: default
2020-07-10 08:01:21.143 INFO 879 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2020-07-10 08:01:21.155 INFO 879 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2020-07-10 08:01:21.155 INFO 879 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.36]
2020-07-10 08:01:21.226 INFO 879 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2020-07-10 08:01:21.226 INFO 879 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1051 ms
2020-07-10 08:01:21.263 WARN 879 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a' defined in file [/Users/haolonglong/IdeaProjects/project_boot/target/classes/com/test/demo/service/A.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in file [/Users/haolonglong/IdeaProjects/project_boot/target/classes/com/test/demo/service/B.class]: Unsatisfied dependency expressed through constructor parameter 0; 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?
2020-07-10 08:01:21.265 INFO 879 --- [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2020-07-10 08:01:21.276 INFO 879 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-07-10 08:01:21.278 ERROR 879 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| a defined in file [/Users/haolonglong/IdeaProjects/project_boot/target/classes/com/test/demo/service/A.class]
↑ ↓
| b defined in file [/Users/haolonglong/IdeaProjects/project_boot/target/classes/com/test/demo/service/B.class]
└─────┘
Process finished with exit code 1
當(dāng)你有一個(gè)循環(huán)依賴,很可能你有一個(gè)設(shè)計(jì)問(wèn)題并且各責(zé)任沒(méi)有得到很好的分離。你應(yīng)該盡量正確地重新設(shè)計(jì)組件,以便它們的層次是精心設(shè)計(jì)的,也沒(méi)有必要循環(huán)依賴。如果不能重新設(shè)計(jì)組件(可能有很多的原因:遺留代碼,已經(jīng)被測(cè)試并不能修改代碼,沒(méi)有足夠的時(shí)間或資源來(lái)完全重新設(shè)計(jì)......),但有一些變通方法來(lái)解決這個(gè)問(wèn)題。
解決Spring 循環(huán)依賴的一個(gè)簡(jiǎn)單方法就是對(duì)一個(gè)Bean使用延時(shí)加載。也就是說(shuō):這個(gè)Bean并沒(méi)有完全的初始化完,實(shí)際上他注入的是一個(gè)代理,只有當(dāng)他首次被使用的時(shí)候才會(huì)被完全的初始化。
package com.test.demo.service;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class A {
private B b;
public A (@Lazy B b) {
this.b = b;
}
}
其中最流行的解決方法,就是Spring文檔中建議,使用setter注入。簡(jiǎn)單地說(shuō),你對(duì)你須要注入的bean是使用setter注入(或字段注入),而不是構(gòu)造函數(shù)注入。通過(guò)這種方式創(chuàng)建Bean,實(shí)際上它此時(shí)的依賴并沒(méi)有被注入,只有在你須要的時(shí)候他才會(huì)被注入進(jìn)來(lái)。
package com.test.demo.service;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
public B getB() {
return b;
}
}package com.test.demo.service;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class B {
private A a ;
public void setA(A a) {
this.a = a;
}
public A getA() {
return a;
}
}
打破循環(huán)的另一種方式是,在要注入的屬性(該屬性是一個(gè)bean)上使用 @Autowired ,并使用@PostConstruct 標(biāo)注在另一個(gè)方法,且該方法里設(shè)置對(duì)其他的依賴。我們的Bean將修改成下面的代碼:
如果一個(gè)Bean實(shí)現(xiàn)了ApplicationContextAware,該Bean可以訪問(wèn)Spring上下文,并可以從那里獲取到其他的bean。實(shí)現(xiàn)InitializingBean接口,表明這個(gè)bean在所有的屬性設(shè)置完后做一些后置處理操作(調(diào)用的順序?yàn)閕nit-method后調(diào)用);在這種情況下,我們需要手動(dòng)設(shè)置依賴。
@Component
public class CircularDependencyA implements ApplicationContextAware, InitializingBean {
private CircularDependencyB circB;
private ApplicationContext context;
public CircularDependencyB getCircB() {
return circB;
}
@Override
public void afterPropertiesSet() throws Exception {
circB = context.getBean(CircularDependencyB.class);
}
@Override
public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
context = ctx;
}
}public class CircularDependencyB {
private CircularDependencyA circA;
private String message = "Hi!";
@Autowired
public void setCircA(CircularDependencyA circA) {
this.circA = circA;
}
public String getMessage() {
return message;
}
}同樣,我們可以運(yùn)行之前的測(cè)試,看看有沒(méi)有異常拋出,程序結(jié)果是否是我們所期望的那樣。綜上,一般采用第三種setter的方式和第二種@Lazy延遲加載的策略
關(guān)于Spring bean的創(chuàng)建,其本質(zhì)上還是一個(gè)對(duì)象的創(chuàng)建,既然是對(duì)象,讀者朋友一定要明白一點(diǎn)就是,一個(gè)完整的對(duì)象包含兩部分:當(dāng)前對(duì)象實(shí)例化和對(duì)象屬性的實(shí)例化。在Spring中,對(duì)象的實(shí)例化是通過(guò)反射實(shí)現(xiàn)的,而對(duì)象的屬性則是在對(duì)象實(shí)例化之后通過(guò)一定的方式設(shè)置的。這個(gè)過(guò)程可以按照如下方式進(jìn)行理解:
然后我們看上面解決循環(huán)依賴注入的示例3
@Component
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
public B getB() {
return b;
}
}可以看到,這里A和B中各自都以對(duì)方為自己的全局屬性。這里首先需要說(shuō)明的一點(diǎn)是,Spring實(shí)例化bean是通過(guò)ApplicationContext.getBean()方法來(lái)進(jìn)行的。如果要獲取的對(duì)象依賴了另一個(gè)對(duì)象,那么其首先會(huì)創(chuàng)建當(dāng)前對(duì)象,然后通過(guò)遞歸的調(diào)用ApplicationContext.getBean()方法來(lái)獲取所依賴的對(duì)象,最后將獲取到的對(duì)象注入到當(dāng)前對(duì)象中。這里我們以上面的首先初始化A對(duì)象實(shí)例為例進(jìn)行講解。
圖中g(shù)etBean()表示調(diào)用Spring的ApplicationContext.getBean()方法,而該方法中的參數(shù),則表示我們要嘗試獲取的目標(biāo)對(duì)象。圖中的黑色箭頭表示一開始的方法調(diào)用走向,走到最后,返回了Spring中緩存的A對(duì)象之后,表示遞歸調(diào)用返回了,此時(shí)使用綠色的箭頭表示。從圖中我們可以很清楚的看到,B對(duì)象的a屬性是在第三步中注入的半成品A對(duì)象,而A對(duì)象的b屬性是在第二步中注入的成品B對(duì)象,此時(shí)半成品的A對(duì)象也就變成了成品的A對(duì)象,因?yàn)槠鋵傩砸呀?jīng)設(shè)置完成了。
重點(diǎn)方法流程如下
對(duì)于Spring處理循環(huán)依賴問(wèn)題的方式,我們這里通過(guò)上面的流程圖其實(shí)很容易就可以理解,需要注意的一個(gè)點(diǎn)就是,Spring是如何標(biāo)記開始生成的A對(duì)象是一個(gè)半成品,并且是如何保存A對(duì)象的。
在AbstractBeanFactory.doGetBean()方法中獲取對(duì)象的方法如下:
protected T doGetBean(final String name, @Nullable final Class requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 嘗試通過(guò)bean名稱獲取目標(biāo)bean對(duì)象,比如這里的A對(duì)象
Object sharedInstance = getSingleton(beanName);
// 我們這里的目標(biāo)對(duì)象都是單例的
if (mbd.isSingleton()) {
// 這里就嘗試創(chuàng)建目標(biāo)對(duì)象,第二個(gè)參數(shù)傳的就是一個(gè)ObjectFactory類型的對(duì)象,這里是使用Java8的lamada
// 表達(dá)式書寫的,只要上面的getSingleton()方法返回值為空,則會(huì)調(diào)用這里的getSingleton()方法來(lái)創(chuàng)建
// 目標(biāo)對(duì)象
sharedInstance = getSingleton(beanName, () -> {
try {
// 嘗試創(chuàng)建目標(biāo)對(duì)象
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
throw ex;
}
});
}
return (T) bean;
} 這里的doGetBean()方法是非常關(guān)鍵的一個(gè)方法上面也主要有兩個(gè)步驟,
第一個(gè)getSingleton()方法
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 嘗試從緩存中獲取成品的目標(biāo)對(duì)象,如果存在,則直接返回
Object singletonObject = this.singletonObjects.get(beanName);
// 如果緩存中不存在目標(biāo)對(duì)象,則判斷當(dāng)前對(duì)象是否已經(jīng)處于創(chuàng)建過(guò)程中,
//在前面的講解中,第一次嘗試獲取A對(duì)象的實(shí)例之后,就會(huì)將A對(duì)象標(biāo)記為正在創(chuàng)建中,
//因而最后再嘗試獲取A對(duì)象的時(shí)候,這里的if判斷就會(huì)為true
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 這里的singletonFactories是一個(gè)Map,其key是bean的名稱,
//而值是一個(gè)ObjectFactory類型的對(duì)象,
//這里對(duì)于A和B而言,調(diào)用圖其getObject()方法返回的就是A和B對(duì)象的實(shí)例,無(wú)論是否是半成品
ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 獲取目標(biāo)對(duì)象的實(shí)例
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}這里我們會(huì)存在一個(gè)問(wèn)題就是A的半成品實(shí)例是如何實(shí)例化的,然后是如何將其封裝為一個(gè)ObjectFactory類型的對(duì)象,并且將其放到上面的singletonFactories屬性中的。
這主要是在前面的第二個(gè)getSingleton()方法中,其最終會(huì)通過(guò)其傳入的第二個(gè)參數(shù),從而調(diào)用
createBean()方法,該方法的最終調(diào)用是委托給了另一個(gè)doCreateBean()方法進(jìn)行的,這里面有如下一段代碼:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 實(shí)例化當(dāng)前嘗試獲取的bean對(duì)象,比如A對(duì)象和B對(duì)象都是在這里實(shí)例化的
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 判斷Spring是否配置了支持提前暴露目標(biāo)bean,也就是是否支持提前暴露半成品的bean
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences
&& isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 如果支持,為了解決循環(huán)依賴,再bean初始化完成前將創(chuàng)建實(shí)例的objectFactory加入工廠
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
try {
// 在初始化實(shí)例之后,這里就是判斷當(dāng)前bean是否依賴了其他的bean,如果依賴了,
// 就會(huì)遞歸的調(diào)用getBean()方法嘗試獲取目標(biāo)bean
populateBean(beanName, mbd, instanceWrapper);
} catch (Throwable ex) {
// 省略...
}
return exposedObject;
}到這里,Spring整個(gè)解決循環(huán)依賴問(wèn)題的實(shí)現(xiàn)思路已經(jīng)比較清楚了。對(duì)于整體過(guò)程,讀者朋友只要理解兩點(diǎn):
對(duì)于單例來(lái)說(shuō),在Spring容器整個(gè)生命周期內(nèi),有且只有一個(gè)對(duì)象,所以很容易想到這個(gè)對(duì)象應(yīng)該存在Cache中,Spring為了解決單例的循環(huán)依賴問(wèn)題,使用了三級(jí)緩存。
首先我們看源碼,三級(jí)緩存主要指:
/** Cache of singleton objects: bean name --> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map earlySingletonObjects = new HashMap(16); 這三級(jí)緩存分別指:
我們?cè)趧?chuàng)建bean的時(shí)候,首先想到的是從cache中獲取這個(gè)單例的bean,這個(gè)緩存就是singletonObjects。主要調(diào)用方法就就是:getSingleton(String beanName, boolean allowEarlyReference)
分析getSingleton()的整個(gè)過(guò)程,Spring首先從一級(jí)緩存singletonObjects中獲取。如果獲取不到,并且對(duì)象正在創(chuàng)建中,就再?gòu)亩?jí)緩存earlySingletonObjects中獲取。如果還是獲取不到且允許singletonFactories通過(guò)getObject()獲取,就從三級(jí)緩存singletonFactory.getObject()(三級(jí)緩存)獲取,如果獲取到了則從singletonFactories中移除,并放入earlySingletonObjects中。其實(shí)也就是從三級(jí)緩存移動(dòng)到了二級(jí)緩存。
1 使用一級(jí)緩存
問(wèn)題:這種基本流程是通的,但是如果在整個(gè)流程進(jìn)行中,有另一個(gè)線程要來(lái)取A,那么有可能拿到的只是一個(gè)屬性都為null的半成品A,這樣就會(huì)有問(wèn)題。
2 使用二級(jí)緩存
a)使用singletonObjects和earlySingletonObjects
成品放在singletonObjects中,半成品放在earlySingletonObjects中,流程如下:
問(wèn)題:這樣的流程是線程安全的,不過(guò)如果A上加個(gè)切面(AOP),這種做法就沒(méi)法滿足需求了,因?yàn)閑arlySingletonObjects中存放的都是原始對(duì)象,而我們需要注入的其實(shí)是A的代理對(duì)象。
b)使用singletonObjects和singletonFactories
成品放在singletonObjects中,半成品通過(guò)singletonFactories來(lái)獲取,流程如下:
問(wèn)題:這樣的流程也適用于普通的IOC以及有并發(fā)的場(chǎng)景,但如果A上加個(gè)切面(AOP)的話,這種情況也無(wú)法滿足需求。因?yàn)椴豢赡苊看螆?zhí)行singleFactory.getObject()方法都給我產(chǎn)生一個(gè)新的代理對(duì)象,所以還要借助另外一個(gè)緩存來(lái)保存產(chǎn)生的代理對(duì)象。

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流