Spring循環(huán)依賴問題

先看面試中的連環(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)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容