先看面試中的連環(huán)炮:
- 1.什么是循環(huán)依賴?
- 2.如何檢測(cè)是否存在循環(huán)依賴?
- 3.如何解決循環(huán)依賴?
- 4.多例的情況下,循環(huán)依賴問題為什么無法解決?
- 5.單例的情況下,雖然可以解決循環(huán)依賴,但會(huì)不會(huì)存在其他問題?
- 6.為什么采用三級(jí)緩存解決循環(huán)依賴?如果直接將早期bean丟到二級(jí)緩存可以么?
什么是循環(huán)依賴?
這個(gè)很好理解,多個(gè)bean之間相互依賴,形成一個(gè)閉環(huán)。
public class A{
B b;
}
public class B{
C c;
}
public class C{
A a;
}
如何檢測(cè)是否存在循環(huán)依賴?
檢測(cè)循環(huán)依賴比較簡(jiǎn)單,使用一個(gè)列表來記錄正在創(chuàng)建中的bean,bean創(chuàng)建之前,先去記錄中看一下自己是否已經(jīng)在列表中了,如果在,說明存在循環(huán)依賴,如果不在,則將其加入到這個(gè)列表,bean創(chuàng)建完畢之后,將其再?gòu)倪@個(gè)列表中移除。
源碼方面Spring創(chuàng)建單例bean的時(shí)候,會(huì)調(diào)用下面方法,在AbstractBeanFactory中:
protected void beforeSingletonCreation(String beanName){
if( !this.inCreationCheckExclusions.contains(beanName) &&
!this.singletonsCurrentlyIncreation.add(beanName)){
throw new BeanCurrentlyIncreationException(beanName);
}
}
singletonsCurentlyIncreation就是用來記錄目前正在創(chuàng)建中的bean名稱列表Set<String>,this.singletonsCurrentlyInCreation.add(beanName)返回false,說明beanName已經(jīng)在列表中了,此時(shí)會(huì)拋循環(huán)依賴的異常BeanCurrentlyIncreationException,
對(duì)應(yīng)的源碼:
public BeanCurrentlyInCreationException(String beanName){
super(beanName, "Requested bean is currently in creation: Is there an unresolvable circular reference?");
}
上面是單例bean檢車循環(huán)依賴的源碼,接下來看看非單例的情況:
以propotype情況為例,源碼位于org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean方法中:
//檢查正在創(chuàng)建的bean列表(ThreadLocal)中是否存在beanName,如果存在,說明存在循環(huán)依賴,拋出循環(huán)依賴異常
if( isPropertypeCurrentlyCreation(beanName) ){
throw new BeanCurrentlyInCreationException(beanName);
}
//判斷scope是不是propertype
if( mbd.isPropertype() ){
Object propertypeInstance = null;
try {
//將beanName方法正在創(chuàng)建的列表中
beforePropertypeCreation(beanName);
propertypeInstance = createBean(beanName, mbd, args);
}finally{
//將beanName從正在創(chuàng)建的列表中移除
}
}
Spring如何解決循環(huán)依賴的問題
Spring創(chuàng)建bean主要的幾個(gè)步驟:
1.實(shí)例化bean,即調(diào)用構(gòu)造器創(chuàng)建bean實(shí)例
2.填充屬性,注入依賴的bean,比如通過set方式,@Autowired注解方式等
3.bean初始化,比如調(diào)用init方法等
從上面可以看出,注入依賴的對(duì)象,有兩種情況:
1.構(gòu)造器的方式注入依賴
2.填充屬性注入,set和注解
先來看用構(gòu)造器方式注入依賴的bean,下面兩個(gè)bean循環(huán)依賴:
@Component
public class ServiceA{
private ServiceB serviceB;
public ServiceA(ServiceB serviceB){
this.serviceB = serviceB;
}
}
@Component
public class ServiceB {
private ServiceA serviceA;
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
構(gòu)造器的情況比較容易理解,實(shí)例化ServiceA的時(shí)候,需要有serviceB,而實(shí)例化ServiceB的時(shí)候需要有serviceA,構(gòu)造器循環(huán)依賴是無法解決的,大家可以嘗試一下使用編碼的方式創(chuàng)建上面2個(gè)對(duì)象,是無法創(chuàng)建成功的!
再來看看非構(gòu)造器的方式注入相互依賴的bean,以set方式注入為例,下面是2個(gè)單例的bean:serviceA和serviceB:
@Component
public class ServiceA {
private ServiceB serviceB;
@Autowired
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Component
public class ServiceB {
private ServiceA serviceA;
@Autowired
public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
如果我們采用硬編碼的方式創(chuàng)建上面2個(gè)對(duì)象,過程如下:
//創(chuàng)建serviceA
ServiceA serviceA = new ServiceA();
//創(chuàng)建serviceB
ServiceB serviceB = new ServiceB();
//將serviceA注入到serviceB中
serviceB.setServiceA(serviceA);
//將serviceB注入到serviceA中
serviceA.setServiceB(serviceB);
由于單例bean在spring容器中只存在一個(gè),所以spring容器中肯定是有一個(gè)緩存來存放所有已創(chuàng)建好的單例bean;獲取單例bean之前,可以先去緩存中找,找到了直接返回,找不到的情況下再去創(chuàng)建,創(chuàng)建完畢之后再將其丟到緩存中??梢允褂靡粋€(gè)map來存儲(chǔ)單例bean,比如下面這個(gè):
Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
下面繼續(xù)來看一下spring中set方法創(chuàng)建上面2個(gè)bean的過程:
1.spring輪詢準(zhǔn)備創(chuàng)建2個(gè)bean:serviceA和serviceB
2.spring容器發(fā)現(xiàn)singletonObjects中沒有serviceA
3.調(diào)用serviceA的構(gòu)造器創(chuàng)建serviceA實(shí)例
4.serviceA準(zhǔn)備注入依賴的對(duì)象,發(fā)現(xiàn)需要通過setServiceB注入serviceB
5.serviceA向spring容器查找serviceB
6.spring容器發(fā)現(xiàn)singletonObjects中沒有serviceB
7.調(diào)用serviceB的構(gòu)造器創(chuàng)建serviceB實(shí)例
8.serviceB準(zhǔn)備注入依賴的對(duì)象,發(fā)現(xiàn)需要通過setServiceA注入serviceA
9.serviceB向spring容器查找serviceA
10.此時(shí)又進(jìn)入步驟2了
上面就形成了死循環(huán),怎么才能終結(jié)呢?
可以在第3步后加一個(gè)操作:將實(shí)例化好的serviceA丟到singletonObjects中,此時(shí)問題就解決了。
Spring中也采用類似的方式,稍微有點(diǎn)區(qū)別,上面使用了一個(gè)緩存,而spring內(nèi)部采用了3級(jí)緩存來解決這個(gè)問題,我們一起來細(xì)看一下:
/** 第一級(jí)緩存:?jiǎn)卫齜ean的緩存 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 第二級(jí)緩存:早期暴露的bean的緩存 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** 第三級(jí)緩存:?jiǎn)卫齜ean工廠的緩存 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
看一下Spring中的具體過程:開始的時(shí)候,獲取serviceA,調(diào)用下面的代碼:
//org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
//1.查看緩存中是否已經(jīng)有這個(gè)bean了
Object sharedInstance = this.getSingleton(beanName);
if (sharedInstance != null && args == null) {
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}else{
//若緩存不存在,準(zhǔn)備創(chuàng)建這個(gè)bean
if( mbd.isSingleton() ){
//2.進(jìn)入創(chuàng)建bean的創(chuàng)建過程
sharedInstance = getSingleton(beanName, ()->{
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
return (T) bean;
}
- 1:查看緩存中是否已經(jīng)有了這個(gè)bean了:
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
進(jìn)入getSingleton方法,會(huì)嘗試從3級(jí)緩存中查找bean,注意下面第二個(gè)參數(shù),為ture的時(shí)候,才會(huì)從第3級(jí)中查找,否則只會(huì)查找1、2級(jí)緩存:
//allowEarlyReference:是否允許從三級(jí)緩存singletonFactories中通過getObject拿到bean
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//1.先從一級(jí)緩存中找
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//2.從二級(jí)緩存中找
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//3.二級(jí)緩存中沒找到 && allowEarlyReference為true的情況下,從三級(jí)緩存中找
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//三級(jí)緩存返回的是一個(gè)工廠,通過工廠來獲取創(chuàng)建bean
singletonObject = singletonFactory.getObject();
//將創(chuàng)建好的bean丟到二級(jí)緩存中
this.earlySingletonObjects.put(beanName, singletonObject);
//從三級(jí)緩存移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
剛開始,3個(gè)緩存中肯定是找不到的,會(huì)返回null,接著會(huì)執(zhí)行下面代碼準(zhǔn)備創(chuàng)建serviceA:
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> { //@1
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
}
進(jìn)入@1getSingleton方法,精簡(jiǎn)代碼如下:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//單例bean創(chuàng)建之前調(diào)用,將其加入正在創(chuàng)建的列表中,上面有提到過,主要用來檢測(cè)循環(huán)依賴用的
beforeSingletonCreation(beanName);
boolean newSingleton = false;
try {
//調(diào)用工廠創(chuàng)建bean
singletonObject = singletonFactory.getObject();//@1
newSingleton = true;
}
finally {
//單例bean創(chuàng)建之前調(diào)用,主要是將其從正在創(chuàng)建的列表中移除
afterSingletonCreation(beanName);
}
if (newSingleton) {
//將創(chuàng)建好的單例bean放入緩存中
addSingleton(beanName, singletonObject);//@2
}
}
return singletonObject;
}
}
上面@1和@2是關(guān)鍵代碼,先來看一下@1,這個(gè)是一個(gè)ObjectFactory類型的,從外面?zhèn)魅氲模缦?
if (mbd.isSingleton()) {
sharedInstance = this.getSingleton(beanName, () -> {
try {
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throw var5;
}
});
bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
而createBean最終會(huì)調(diào)用下面的方法:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
beanInstance = this.doCreateBean(beanName, mbdToUse, args);
代碼如下:
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
//通過反射調(diào)用構(gòu)造器實(shí)例化serviceA
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//變量bean:表示剛剛同構(gòu)造器創(chuàng)建好的bean示例
final Object bean = instanceWrapper.getWrappedInstance();
//判斷是否需要暴露早期的bean,條件為(是否是單例bean && 當(dāng)前容器允許循環(huán)依賴 && bean名稱存在于正在創(chuàng)建的bean名稱清單中)
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
//若earlySingletonExposure為true,通過下面代碼將早期的bean暴露出去
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));//@1
}
這里需要理解一下什么是早期bean:剛剛實(shí)例化好的bean就是早期的bean,此時(shí)bean還未進(jìn)行屬性天從,初始化等操作
@1:通過addSingletonFactory用于將早期的bean暴露出去,主要是丟到第三級(jí)緩存中:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
//第1級(jí)緩存中不存在bean
if (!this.singletonObjects.containsKey(beanName)) {
//將其丟到第3級(jí)緩存中
this.singletonFactories.put(beanName, singletonFactory);
//后面的2行代碼不用關(guān)注
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
執(zhí)行完上面的方法后,serviceA酒杯都到第三級(jí)緩存中了。
后續(xù)的過程serviceA開始注入依賴的對(duì)象,發(fā)現(xiàn)需要注入serviceB,會(huì)從容器中獲取serviceB,而serviceB的獲取又會(huì)走上面同樣的過程實(shí)例化serviceB,然后將serviceB提前暴露出去,然后serviceB開始注入依賴的對(duì)象,serviceB發(fā)現(xiàn)自己需要注入serviceA,此時(shí)去容器中找serviceA,找serviceA會(huì)先去緩存中找,會(huì)執(zhí)行
getSingleton("serviceA", true)``,代碼如下:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//1.先從一級(jí)緩存中找
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//2.從二級(jí)緩存中找
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//3.二級(jí)緩存中沒找到 && allowEarlyReference為true的情況下,從三級(jí)緩存中找
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//三級(jí)緩存返回的是一個(gè)工廠,通過工廠來獲取創(chuàng)建bean
singletonObject = singletonFactory.getObject();
//將創(chuàng)建好的bean丟到二級(jí)緩存中
this.earlySingletonObjects.put(beanName, singletonObject);
//從三級(jí)緩存移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
上面的方法走完之后,serviceA會(huì)被放入二級(jí)緩存earlySingletonObjects中,會(huì)將serviceA返回,此時(shí)serviceB中的serviceA注入成功,serviceB繼續(xù)完成創(chuàng)建,然后將自己返回給serviceA,此時(shí)serviceA通過set方法將serviceB注入。
serviceA創(chuàng)建完畢之后,會(huì)調(diào)用addSingleton方法將其加入到緩存中,這塊代碼如下:
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
//將bean放入第1級(jí)緩存中
this.singletonObjects.put(beanName, singletonObject);
//將其從第3級(jí)緩存中移除
this.singletonFactories.remove(beanName);
//將其從第2級(jí)緩存中移除
this.earlySingletonObjects.remove(beanName);
}
}
到此,serviceA和serviceB之間的循環(huán)依賴注入就完成了。
總結(jié):
1.從容器中獲取serviceA
2.容器嘗試從3個(gè)緩存中找serviceA,找不到
3.準(zhǔn)備創(chuàng)建serviceA
4.調(diào)用serviceA的構(gòu)造器創(chuàng)建serviceA,得到serviceA實(shí)例,此時(shí)serviceA還未填充屬性,未進(jìn)行其他任何初始化的操作
5.將早期的serviceA暴露出去:即將其丟到第3級(jí)緩存singletonFactories中
6.serviceA準(zhǔn)備填充屬性,發(fā)現(xiàn)需要注入serviceB,然后向容器獲取serviceB
7.容器嘗試從3個(gè)緩存中找serviceB,找不到
8.準(zhǔn)備創(chuàng)建serviceB
9.調(diào)用serviceB的構(gòu)造器創(chuàng)建serviceB,得到serviceB實(shí)例,此時(shí)serviceB還未填充屬性,未進(jìn)行其他任何初始化的操作
10.將早期的serviceB暴露出去:即將其丟到第3級(jí)緩存singletonFactories中
11.serviceB準(zhǔn)備填充屬性,發(fā)現(xiàn)需要注入serviceA,然后向容器獲取serviceA
12.容器嘗試從3個(gè)緩存中找serviceA,發(fā)現(xiàn)此時(shí)serviceA位于第3級(jí)緩存中,經(jīng)過處理之后,serviceA會(huì)從第3級(jí)緩存中移除,然后會(huì)存到第2級(jí)緩存中,同時(shí)將其返回給serviceB,此時(shí)serviceA通過serviceB中的setServiceA方法被注入到serviceB中。
13.serviceB繼續(xù)執(zhí)行后續(xù)的一些操作,最后完成創(chuàng)建工作,然后會(huì)調(diào)用addSingleton方法,將自己丟到第1級(jí)緩存中,并將自己從第2和第3級(jí)緩存中移除
14.serviceB將自己返回給serviceA
15.serviceA通過setServiceB方法將serviceB注入進(jìn)去
16.serviceA繼續(xù)執(zhí)行后續(xù)的一些操作,最后完成創(chuàng)建工作,然后會(huì)調(diào)用addSingleton方法,將自己丟到第1級(jí)緩存中,并將自己從第2和第3級(jí)緩存中移除
循環(huán)依賴無法解決的情況
只有單例的bean會(huì)通過三級(jí)緩存提前暴露來解決循環(huán)依賴的問題,而非單例的bean,每次從容器中獲取都是一個(gè)新的對(duì)象,都會(huì)重新創(chuàng)建,所以非單例的bean是沒有緩存的,不會(huì)將其放到三級(jí)緩存中。
那下面幾種情況就需要注意:
情況1
條件
serviceA:多例
serviceB:多例
結(jié)果
此時(shí)不管是任何方式都是無法解決循環(huán)依賴的問題,最終都會(huì)報(bào)錯(cuò),因?yàn)槊看稳カ@取依賴的bean都會(huì)重新創(chuàng)建。
情況2
條件
serviceA:?jiǎn)卫?br>
serviceB:多例
結(jié)果
- 若使用構(gòu)造器的方式相互注入,是無法完成注入操作的,會(huì)報(bào)錯(cuò)。
- 若采用set方式注入,所有bean都還未創(chuàng)建的情況下,若去容器中獲取serviceB,會(huì)報(bào)錯(cuò),為什么?我們來看一下過程:
1.從容器中獲取serviceB
2.serviceB由于是多例的,所以緩存中肯定是沒有的
3.檢查serviceB是在正在創(chuàng)建的bean名稱列表中,沒有
4.準(zhǔn)備創(chuàng)建serviceB
5.將serviceB放入正在創(chuàng)建的bean名稱列表中
6.實(shí)例化serviceB(由于serviceB是多例的,所以不會(huì)提前暴露,必須是單例的才會(huì)暴露)
7.準(zhǔn)備填充serviceB屬性,發(fā)現(xiàn)需要注入serviceA
8.從容器中查找serviceA
9.嘗試從3級(jí)緩存中找serviceA,找不到
10.準(zhǔn)備創(chuàng)建serviceA
11.將serviceA放入正在創(chuàng)建的bean名稱列表中
12.實(shí)例化serviceA
13.由于serviceA是單例的,將早期serviceA暴露出去,丟到第3級(jí)緩存中
14.準(zhǔn)備填充serviceA的屬性,發(fā)現(xiàn)需要注入serviceB
15.從容器中獲取serviceB
16.先從緩存中找serviceB,找不到
17.檢查serviceB是在正在創(chuàng)建的bean名稱列表中,發(fā)現(xiàn)已經(jīng)存在了,拋出循環(huán)依賴的異常
如果此處不是去獲取serviceB,而是先去獲取serviceA呢,會(huì)不會(huì)報(bào)錯(cuò)?
探討:為什么需要三級(jí)緩存?
問題:如果只是用2級(jí)緩存,直接將港式梨花好的bean暴露出去是否可以?
答:不行,早期暴露給其他依賴者的bean和最終暴露的bean不一致的問題。若將剛剛實(shí)例化好的bean直接丟到二級(jí)緩存中暴露出去,如果后期這個(gè)bean對(duì)象被更改了,比如可能在上面加了一些攔截器,將其包裝為一個(gè)代理了,那么暴露出去的bean和最終的這個(gè)bean就不一樣的,將自己暴露出去的時(shí)候是一個(gè)原始對(duì)象,而自己最終卻是一個(gè)代理對(duì)象,最終會(huì)導(dǎo)致被暴露出去的和最終的bean不是同一個(gè)bean的,將產(chǎn)生意向不到的效果,而三級(jí)緩存就可以發(fā)現(xiàn)這個(gè)問題,會(huì)報(bào)錯(cuò)。
案例演示:
下面來2個(gè)bean,相互依賴,通過set方法相互注入,并且其內(nèi)部都有一個(gè)m1方法,用來輸出一行日志。
Service1
@Component
public class Service1 {
public void m1() {
System.out.println("Service1 m1");
}
private Service2 service2;
@Autowired
public void setService2(Service2 service2) {
this.service2 = service2;
}
}
Service2
@Component
public class Service2 {
public void m1() {
System.out.println("Service2 m1");
this.service1.m1();//@1
}
private Service1 service1;
@Autowired
public void setService1(Service1 service1) {
this.service1 = service1;
}
public Service1 getService1() {
return service1;
}
}
注意上面的@1,service2的m1方法中會(huì)調(diào)用service1的m1方法。
需求:
在service1上面加個(gè)攔截器,要求在調(diào)用service1的任何方法之前需要先輸出一行日志:你好,service1
實(shí)現(xiàn):
只能贈(zèng)一個(gè)Bean的后置處理器來對(duì)service1對(duì)應(yīng)的bean進(jìn)行處理,將其封裝成一個(gè)代理暴露出去:
@Component
public class MethodBeforeInterceptor implements BeanPostProcessor{
@Nullable
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if("serivice1".equals(beanName)){
//代理創(chuàng)建工廠,需要傳入被代理目標(biāo)
ProxyFactory proxyFactory = new ProxyFactory();
//添加一個(gè)方法前置通知,會(huì)在方法執(zhí)行之前調(diào)用通知中的before()方法
proxyFactory.addAdvice(new MethodBeforeAdvice(){
@Override
public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
System.out.println("你好,service1");
}
});
//返回代理對(duì)象
return proxyFactory.getProxy();
}
return bean;
}
}
上面的postProcessAfterInitialization方法內(nèi)部會(huì)在service1初始化之后調(diào)用,內(nèi)部會(huì)對(duì)service1這個(gè)bean進(jìn)行處理,返回一個(gè)代理對(duì)象,通過代理來訪問service1的方法,訪問service1中的任何方法之前,會(huì)先輸出:你好,service1。
配置類
@Component
public class MainConfig{
}
測(cè)試
@Test
public void test23(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MainConfig3.class);
context.refresh();
}
運(yùn)行,出錯(cuò):
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'service1': Bean with name 'service1' has been injected into other beans [service2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:624)
可以看出是·AbstractAutowireCapableBeanFactory.java:624出錯(cuò):
if (earlySingletonExposure) {
//@1
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
//@2
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
//@3
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
上面代碼主要用來判斷當(dāng)有循環(huán)依賴的情況下,早期暴露給別人使用的bean是否和最終的bean不一樣的情況下,會(huì)拋出一個(gè)異常。
通過代碼級(jí)別的來解釋上面代碼:
@1:調(diào)用getSingleton(beanName, false)方法,這個(gè)方法用來從3個(gè)級(jí)別的緩存中獲取bean,但是注意了,這個(gè)地方第二個(gè)參數(shù)是false,此時(shí)只會(huì)嘗試從第1級(jí)和第2級(jí)緩存中獲取bean,如果能夠獲取到,說明了什么?說明了第2級(jí)緩存中已經(jīng)有這個(gè)bean了,而什么情況下第2級(jí)緩存中會(huì)有bean?說明這個(gè)bean從第3級(jí)緩存中已經(jīng)被別人獲取過,然后從第3級(jí)緩存移到了第2級(jí)緩存中,說明這個(gè)早期的bean被別人通過getSingleton(beanName, true)獲取過
@2:這個(gè)地方用來判斷早期暴露的bean和最終spring容器對(duì)這個(gè)bean走完創(chuàng)建過程之后是否還是同一個(gè)bean,上面我們的service1被代理了,所以這個(gè)地方會(huì)返回false,此時(shí)會(huì)走到@3
@3:allowRawInjectionDespiteWrapping這個(gè)參數(shù)用來控制是否允許循環(huán)依賴的情況下,早期暴露給被人使用的bean在后期是否可以被包裝,通俗點(diǎn)理解就是:是否允許早期給別人使用的bean和最終bean不一致的情況,這個(gè)值默認(rèn)是false,表示不允許,也就是說你暴露給別人的bean和你最終的bean需要是一致的。
而上面代碼注入到service2中的service1是早期的service1,而最終spring容器中的service1變成一個(gè)代理對(duì)象了,早期的和最終的不一致了,而allowRawInjectionDespiteWrapping又是false,所以報(bào)異常了。
那么如何解決這個(gè)問題:
很簡(jiǎn)單,將allowRawInjectionDespiteWrapping設(shè)置為true就可以了,下面改一下代碼如下:
@Test
public void test4() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//創(chuàng)建一個(gè)BeanFactoryPostProcessor:BeanFactory后置處理器
context.addBeanFactoryPostProcessor(beanFactory -> {
if (beanFactory instanceof DefaultListableBeanFactory) {
//將allowRawInjectionDespiteWrapping設(shè)置為true
((DefaultListableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
}
});
context.register(MainConfig3.class);
context.refresh();
System.out.println("容器初始化完畢");
}
上面將allowRawInjectionDespiteWrapping設(shè)置為true了,是通過一個(gè)BeanFactoryPostProcessor來實(shí)現(xiàn)的,在bean創(chuàng)建之前用來干預(yù)BeanFactory的創(chuàng)建過程,可以用來修改BeanFactory中的一些配置。
輸出:容器初始化完畢
然后我們看看加在service1上的攔截器有沒有起效:
//獲取service1
Service1 service1 = context.getBean(Service1.class);
//獲取service2
Service2 service2 = context.getBean(Service2.class);
System.out.println("----A-----");
service2.m1(); //@1
System.out.println("----B-----");
service1.m1(); //@2
System.out.println("----C-----");
System.out.println(service2.getService1() == service1);
查看輸出:
容器初始化完畢
----A-----
Service2 m1
Service1 m1
----B-----
你好,service1
Service1 m1
----C-----
false
發(fā)現(xiàn)service2.m1方法中調(diào)用了service1.m1,攔截器沒有起效,但是單獨(dú)調(diào)用service1.m1方法,攔截器起效了,說明service2中注入的service1不是代理對(duì)象,而是早期的service1,注入時(shí)service1還不是一個(gè)代理對(duì)象。
再看看最后一行輸出為false,說明service2中的service1確實(shí)和spring容器中的service1不是一個(gè)對(duì)象了。
既然最終service1是一個(gè)代理對(duì)象,那么你提前暴露出去的時(shí)候,注入到service2的時(shí)候,你也必須得是個(gè)代理對(duì)象啊,需要確保給別人和最終是同一個(gè)對(duì)象。
看暴露早期的bean的源碼:
addSingletonFactory(beanName, ()->getEarlyBeanReference(beanName, mbd, bean));
注意看ferEarlyBeanReference方法:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
從3級(jí)緩存中獲取bean的時(shí)候,會(huì)調(diào)用上面這個(gè)方法來獲取bean,這個(gè)方法內(nèi)部會(huì)看一下容器中是否有SmartInstantiationAwareBeanPostProcessor處理器,依次調(diào)用處理器中的getEarlyBeanReference方法。那么我們可以自己定義一個(gè):
SmartInstantiationAwareBeanPostProcessor,然后在其getEarlyBeanReference中來創(chuàng)建代理,我們將MethodBeforeInterceptor代碼改成這樣:
@Component
public class MethodBeforeInterceptor implements SmartInstantiationAwareBeanPostProcessor {
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
if ("service1".equals(beanName)) {
//代理創(chuàng)建工廠,需傳入被代理的目標(biāo)對(duì)象
ProxyFactory proxyFactory = new ProxyFactory(bean);
//添加一個(gè)方法前置通知,會(huì)在方法執(zhí)行之前調(diào)用通知中的before方法
proxyFactory.addAdvice(new MethodBeforeAdvice() {
@Override
public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
System.out.println("你好,service1");
}
});
//返回代理對(duì)象
return proxyFactory.getProxy();
}
return bean;
}
}
測(cè)試,輸出得要我們想要的結(jié)果。
單例bean解決循環(huán)依賴,還存在什么問題?
循環(huán)依賴的情況下,由于注入的是早期的bean,此時(shí)早期的bean中還未被填充屬性,初始化等各種操作,也就是說此時(shí)bean并沒有被完全初始化完畢,此時(shí)若直接拿去使用,可能存在有問題的風(fēng)險(xiǎn)。
(轉(zhuǎn)自路人甲Java)