spring循環(huán)依賴與三級(jí)緩存

什么是循環(huán)依賴?
循環(huán)依賴其實(shí)就是循環(huán)引用,也就是兩個(gè)或則兩個(gè)以上的bean互相持有對(duì)方,最終形成閉環(huán)。比如A依賴于B,B依賴于C,C又依賴于A。

image.png

可以設(shè)想一下這個(gè)場(chǎng)景:如果在日常開(kāi)發(fā)中我們用new對(duì)象的方式,若構(gòu)造函數(shù)之間發(fā)生這種循環(huán)依賴的話,程序會(huì)在運(yùn)行時(shí)一直循環(huán)調(diào)用最終導(dǎo)致內(nèi)存溢出,示例代碼如下:

public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println(new A());
    }
}

class A {
    public A() {
        new B();
    }
}

class B {
    public B() {
        new A();
    }
}

運(yùn)行結(jié)果會(huì)拋出Exception in thread "main" java.lang.StackOverflowError異常
這是一個(gè)典型的循環(huán)依賴問(wèn)題。本文說(shuō)一下Spring是如果巧妙的解決平時(shí)我們會(huì)遇到的三大循環(huán)依賴問(wèn)題的~

Spring Bean的循環(huán)依賴

談到Spring Bean的循環(huán)依賴,有的小伙伴可能比較陌生,畢竟開(kāi)發(fā)過(guò)程中好像對(duì)循環(huán)依賴這個(gè)概念無(wú)感知。其實(shí)不然,你有這種錯(cuò)覺(jué),權(quán)是因?yàn)槟愎ぷ髟赟pring的襁褓中,從而讓你“高枕無(wú)憂”~
我十分堅(jiān)信,小伙伴們?cè)谄綍r(shí)業(yè)務(wù)開(kāi)發(fā)中一定一定寫(xiě)過(guò)如下結(jié)構(gòu)的代碼:
field屬性注入(setter方法注入)循環(huán)依賴
這種方式是我們最為常用的依賴注入方式

@Service
class A {
    @Autowired
    private B b;
}

@Service
class B {
    @Autowired
    private A a;
}

這其實(shí)就是Spring環(huán)境下典型的循環(huán)依賴場(chǎng)景。但是很顯然,這種循環(huán)依賴場(chǎng)景,Spring已經(jīng)完美的幫我們解決和規(guī)避了問(wèn)題。所以即使平時(shí)我們這樣循環(huán)引用,也能夠整成進(jìn)行我們的coding之旅~

Spring中構(gòu)造器依賴場(chǎng)演示

在Spring環(huán)境中,因?yàn)槲覀兊腂ean的實(shí)例化、初始化都是交給了容器,因此它的循環(huán)依賴主要表現(xiàn)為下面三種場(chǎng)景。為了方便演示,我準(zhǔn)備了如下兩個(gè)類:

@Service
public class A {
    public A(B b) {
    }
}
@Service
public class B {
    public B(A a) {
    }
}

結(jié)果:項(xiàng)目啟動(dòng)失敗拋出異常BeanCurrentlyInCreationException

構(gòu)造器注入構(gòu)成的循環(huán)依賴,此種循環(huán)依賴方式是無(wú)法解決的,只能拋出BeanCurrentlyInCreationException異常表示循環(huán)依賴。這也是構(gòu)造器注入的最大劣勢(shì)。

根本原因:Spring解決循環(huán)依賴依靠的是Bean的“中間態(tài)”這個(gè)概念,而這個(gè)中間態(tài)指的是已經(jīng)實(shí)例化,但還沒(méi)初始化的狀態(tài)。而構(gòu)造器是完成實(shí)例化的,所以構(gòu)造器的循環(huán)依賴無(wú)法解決

對(duì)Bean的創(chuàng)建最為核心三個(gè)方法解釋如下:

  • createBeanInstance:例化,其實(shí)也就是調(diào)用對(duì)象的構(gòu)造方法實(shí)例化對(duì)象
  • populateBean:填充屬性,這一步主要是對(duì)bean的依賴屬性進(jìn)行注入(@Autowired)
  • initializeBean:回到一些形如initMethod、InitializingBean等方法

從對(duì)單例Bean的初始化可以看出,循環(huán)依賴主要發(fā)生在第二步(populateBean),也就是field屬性注入的處理。

Spring容器的三級(jí)緩存

在Spring容器的整個(gè)聲明周期中,單例Bean有且僅有一個(gè)對(duì)象。這很容易讓人想到可以用緩存來(lái)加速訪問(wèn)。
從源碼中也可以看出Spring大量運(yùn)用了Cache的手段,在循環(huán)依賴問(wèn)題的解決過(guò)程中甚至不惜使用了“三級(jí)緩存”,這也便是它設(shè)計(jì)的精妙之處~

三級(jí)緩存其實(shí)它更像是Spring容器工廠的內(nèi)的術(shù)語(yǔ),采用三級(jí)緩存模式來(lái)解決循環(huán)依賴問(wèn)題,這三級(jí)緩存分別指:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    ...
    // 從上至下 分表代表這“三級(jí)緩存”
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); //一級(jí)緩存
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 二級(jí)緩存
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 三級(jí)緩存
    ...
    
    /** Names of beans that are currently in creation. */
    // 這個(gè)緩存也十分重要:它表示bean創(chuàng)建過(guò)程中都會(huì)在里面呆著~
    // 它在Bean開(kāi)始創(chuàng)建時(shí)放值,創(chuàng)建完成時(shí)會(huì)將其移出~
    private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

    /** Names of beans that have already been created at least once. */
    // 當(dāng)這個(gè)Bean被創(chuàng)建完成后,會(huì)標(biāo)記為這個(gè) 注意:這里是set集合 不會(huì)重復(fù)
    // 至少被創(chuàng)建了一次的  都會(huì)放進(jìn)這里~~~~
    private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));

注:AbstractBeanFactory繼承自DefaultSingletonBeanRegistry~

singletonObjects:用于存放完全初始化好的 bean,從該緩存中取出的 bean 可以直接使用
earlySingletonObjects:提前曝光的單例對(duì)象的cache,存放原始的 bean 對(duì)象(尚未填充屬性),用于解決循環(huán)依賴
singletonFactories:?jiǎn)卫龑?duì)象工廠的cache,存放 bean 工廠對(duì)象,用于解決循環(huán)依賴

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    ...
    @Override
    @Nullable
    public Object getSingleton(String beanName) {
        return getSingleton(beanName, true);
    }
    @Nullable
    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;
    }
    ...
    public boolean isSingletonCurrentlyInCreation(String beanName) {
        return this.singletonsCurrentlyInCreation.contains(beanName);
    }
    protected boolean isActuallyInCreation(String beanName) {
        return isSingletonCurrentlyInCreation(beanName);
    }
    ...
}

先從一級(jí)緩存singletonObjects中去獲取。(如果獲取到就直接return)
如果獲取不到或者對(duì)象正在創(chuàng)建中(isSingletonCurrentlyInCreation()),那就再?gòu)亩?jí)緩存earlySingletonObjects中獲取。(如果獲取到就直接return)
如果還是獲取不到,且允許singletonFactories(allowEarlyReference=true)通過(guò)getObject()獲取。就從三級(jí)緩存singletonFactory.getObject()獲取。(如果獲取到了就從singletonFactories中移除,并且放進(jìn)earlySingletonObjects。其實(shí)也就是從三級(jí)緩存移動(dòng)(是剪切、不是復(fù)制哦~)到了二級(jí)緩存)
加入singletonFactories三級(jí)緩存的前提是執(zhí)行了構(gòu)造器,所以構(gòu)造器的循環(huán)依賴沒(méi)法解決

getSingleton()從緩存里獲取單例對(duì)象步驟分析可知,Spring解決循環(huán)依賴的訣竅:就在于singletonFactories這個(gè)三級(jí)緩存。這個(gè)Cache里面都是ObjectFactory,它是解決問(wèn)題的關(guān)鍵。

為什么要用三級(jí)緩存而不是二級(jí)緩存

image.png

可以看到三級(jí)緩存各自保存的對(duì)象,這里重點(diǎn)關(guān)注二級(jí)緩存earlySingletonObjects和三級(jí)緩存singletonFactory,一級(jí)緩存可以進(jìn)行忽略。前面我們講過(guò)先實(shí)例化的bean會(huì)通過(guò)ObjectFactory半成品提前暴露在三級(jí)緩存中
所以如果沒(méi)有AOP的話確實(shí)可以兩級(jí)緩存就可以解決循環(huán)依賴的問(wèn)題,如果加上AOP,兩級(jí)緩存是無(wú)法解決的,不可能每次執(zhí)行singleFactory.getObject()方法都給我產(chǎn)生一個(gè)新的代理對(duì)象,所以還要借助另外一個(gè)緩存來(lái)保存產(chǎn)生的代理對(duì)象

靜態(tài)代理

靜態(tài)代理的特點(diǎn)是, 為每一個(gè)業(yè)務(wù)增強(qiáng)都提供一個(gè)代理類, 由代理類來(lái)創(chuàng)建代理對(duì)象. 下面我們通過(guò)靜態(tài)代理來(lái)實(shí)現(xiàn)對(duì)轉(zhuǎn)賬業(yè)務(wù)進(jìn)行身份驗(yàn)證.

(1) 轉(zhuǎn)賬業(yè)務(wù)

public interface IAccountService {
    //主業(yè)務(wù)邏輯: 轉(zhuǎn)賬
    void transfer();
}
public class AccountServiceImpl implements IAccountService {
    @Override
    public void transfer() {
        System.out.println("調(diào)用dao層,完成轉(zhuǎn)賬主業(yè)務(wù).");
    }
}

(2) 代理類

public class AccountProxy implements IAccountService {
    //目標(biāo)對(duì)象
    private IAccountService target;

    public AccountProxy(IAccountService target) {
        this.target = target;
    }

    /**
     * 代理方法,實(shí)現(xiàn)對(duì)目標(biāo)方法的功能增強(qiáng)
     */
    @Override
    public void transfer() {
        before();
        target.transfer();
    }

    /**
     * 前置增強(qiáng)
     */
    private void before() {
        System.out.println("對(duì)轉(zhuǎn)賬人身份進(jìn)行驗(yàn)證.");
    }
}

(3) 測(cè)試

public class Client {
    public static void main(String[] args) {
        //創(chuàng)建目標(biāo)對(duì)象
        IAccountService target = new AccountServiceImpl();
        //創(chuàng)建代理對(duì)象
        AccountProxy proxy = new AccountProxy(target);
        proxy.transfer();
    }
}

結(jié)果: 
對(duì)轉(zhuǎn)賬人身份進(jìn)行驗(yàn)證.
調(diào)用dao層,完成轉(zhuǎn)賬主業(yè)務(wù).

動(dòng)態(tài)代理

靜態(tài)代理會(huì)為每一個(gè)業(yè)務(wù)增強(qiáng)都提供一個(gè)代理類, 由代理類來(lái)創(chuàng)建代理對(duì)象, 而動(dòng)態(tài)代理并不存在代理類, 代理對(duì)象直接由代理生成工具動(dòng)態(tài)生成.

JDK動(dòng)態(tài)代理

JDK動(dòng)態(tài)代理是使用 java.lang.reflect 包下的代理類來(lái)實(shí)現(xiàn). JDK動(dòng)態(tài)代理動(dòng)態(tài)代理必須要有接口.

(1) 轉(zhuǎn)賬業(yè)務(wù)

public interface IAccountService {
    //主業(yè)務(wù)邏輯: 轉(zhuǎn)賬
    void transfer();
}
public class AccountServiceImpl implements IAccountService {
    @Override
    public void transfer() {
        System.out.println("調(diào)用dao層,完成轉(zhuǎn)賬主業(yè)務(wù).");
    }
}

(2) 增強(qiáng)

因?yàn)檫@里沒(méi)有配置切入點(diǎn), 稱為切面會(huì)有點(diǎn)奇怪, 所以稱為增強(qiáng).

public class AccountAdvice implements InvocationHandler {
    //目標(biāo)對(duì)象
    private IAccountService target;

    public AccountAdvice(IAccountService target) {
        this.target = target;
    }

    /**
     * 代理方法, 每次調(diào)用目標(biāo)方法時(shí)都會(huì)進(jìn)到這里
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        return method.invoke(target, args);
    }

    /**
     * 前置增強(qiáng)
     */
    private void before() {
        System.out.println("對(duì)轉(zhuǎn)賬人身份進(jìn)行驗(yàn)證.");
    }
}

(3) 測(cè)試

public class Client {
    public static void main(String[] args) {
        //創(chuàng)建目標(biāo)對(duì)象
        IAccountService target = new AccountServiceImpl();
        //創(chuàng)建代理對(duì)象
        IAccountService proxy = (IAccountService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new AccountAdvice(target)
        );
        proxy.transfer();
    }
}
結(jié)果: 
對(duì)轉(zhuǎn)賬人身份進(jìn)行驗(yàn)證.
調(diào)用dao層,完成轉(zhuǎn)賬主業(yè)務(wù).

CGLIB動(dòng)態(tài)代理

JDK動(dòng)態(tài)代理必須要有接口, 但如果要代理一個(gè)沒(méi)有接口的類該怎么辦呢? 這時(shí)我們可以使用CGLIB動(dòng)態(tài)代理. CGLIB動(dòng)態(tài)代理的原理是生成目標(biāo)類的子類, 這個(gè)子類對(duì)象就是代理對(duì)象, 代理對(duì)象是被增強(qiáng)過(guò)的.

注意: 不管有沒(méi)有接口都可以使用CGLIB動(dòng)態(tài)代理, 而不是只有在無(wú)接口的情況下才能使用.

(1) 轉(zhuǎn)賬業(yè)務(wù)

public class AccountService {
    public void transfer() {
        System.out.println("調(diào)用dao層,完成轉(zhuǎn)賬主業(yè)務(wù).");
    }
}

(2) 增強(qiáng)

因?yàn)檫@里沒(méi)有配置切入點(diǎn), 稱為切面會(huì)有點(diǎn)奇怪, 所以稱為增強(qiáng).

public class AccountAdvice implements MethodInterceptor {
    /**
     * 代理方法, 每次調(diào)用目標(biāo)方法時(shí)都會(huì)進(jìn)到這里
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        before();
        return methodProxy.invokeSuper(obj, args);
        //        return method.invoke(obj, args);  這種也行
    }

    /**
     * 前置增強(qiáng)
     */
    private void before() {
        System.out.println("對(duì)轉(zhuǎn)賬人身份進(jìn)行驗(yàn)證.");
    }
}

(3) 測(cè)試

public class Client {
    public static void main(String[] args) {
        //創(chuàng)建目標(biāo)對(duì)象
        AccountService target = new AccountService();
        //
        //創(chuàng)建代理對(duì)象
        AccountService proxy = (AccountService) Enhancer.create(target.getClass(),
                new AccountAdvice());
        proxy.transfer();
    }
}
結(jié)果: 
對(duì)轉(zhuǎn)賬人身份進(jìn)行驗(yàn)證.
調(diào)用dao層,完成轉(zhuǎn)賬主業(yè)務(wù).

參考地址:https://www.cnblogs.com/semi-sub/p/13548479.html
參考地址:https://blog.csdn.net/f641385712/article/details/92801300
參考地址:https://blog.csdn.net/litianxiang_kaola/article/details/85335700

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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