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

可以設(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í)緩存

可以看到三級(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