什么是循環(huán)依賴钠龙?
循環(huán)依賴其實(shí)就是循環(huán)引用炬藤,也就是兩個(gè)或則兩個(gè)以上的bean互相持有對(duì)方,最終形成閉環(huán)碴里。比如A依賴于B沈矿,B依賴于C,C又依賴于A咬腋。
可以設(shè)想一下這個(gè)場景:如果在日常開發(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)依賴問題陵像。本文說一下Spring
是如果巧妙的解決平時(shí)我們會(huì)遇到的三大循環(huán)依賴問題
的~
Spring Bean的循環(huán)依賴
談到Spring Bean
的循環(huán)依賴,有的小伙伴可能比較陌生寇壳,畢竟開發(fā)過程中好像對(duì)循環(huán)依賴
這個(gè)概念無感知醒颖。其實(shí)不然,你有這種錯(cuò)覺壳炎,權(quán)是因?yàn)槟愎ぷ髟赟pring的襁褓
中泞歉,從而讓你“高枕無憂”~
我十分堅(jiān)信,小伙伴們?cè)谄綍r(shí)業(yè)務(wù)開發(fā)中一定一定寫過如下結(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)依賴場景冕广。但是很顯然疏日,這種循環(huán)依賴場景,Spring已經(jīng)完美的幫我們解決和規(guī)避了問題撒汉。所以即使平時(shí)我們這樣循環(huán)引用沟优,也能夠整成進(jìn)行我們的coding之旅~
Spring中構(gòu)造器依賴場演示
在Spring環(huán)境中,因?yàn)槲覀兊腂ean的實(shí)例化睬辐、初始化都是交給了容器挠阁,因此它的循環(huán)依賴主要表現(xiàn)為下面三種場景。為了方便演示溯饵,我準(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)依賴方式是無法解決的,只能拋出BeanCurrentlyInCreationException異常表示循環(huán)依賴丰刊。這也是構(gòu)造器注入的最大劣勢(shì)隘谣。
根本原因:Spring解決循環(huán)依賴依靠的是Bean的“中間態(tài)”這個(gè)概念,而這個(gè)中間態(tài)指的是已經(jīng)實(shí)例化,但還沒初始化的狀態(tài)寻歧。而構(gòu)造器是完成實(shí)例化的掌栅,所以構(gòu)造器的循環(huán)依賴無法解決
對(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ì)象痢站。這很容易讓人想到可以用緩存來加速訪問磷箕。
從源碼中也可以看出Spring大量運(yùn)用了Cache的手段,在循環(huán)依賴問題的解決過程中甚至不惜使用了“三級(jí)緩存”瑟押,這也便是它設(shè)計(jì)的精妙之處~
三級(jí)緩存
其實(shí)它更像是Spring容器工廠的內(nèi)的術(shù)語
搀捷,采用三級(jí)緩存模式來解決循環(huá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)建過程中都會(huì)在里面呆著~
// 它在Bean開始創(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:單例對(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())蜀踏,那就再從二級(jí)緩存earlySingletonObjects中獲取维蒙。(如果獲取到就直接return)
如果還是獲取不到,且允許singletonFactories(allowEarlyReference=true)通過getObject()獲取果覆。就從三級(jí)緩存singletonFactory.getObject()獲取颅痊。(如果獲取到了就從singletonFactories中移除,并且放進(jìn)earlySingletonObjects局待。其實(shí)也就是從三級(jí)緩存移動(dòng)(是剪切斑响、不是復(fù)制哦~)到了二級(jí)緩存)
加入singletonFactories三級(jí)緩存的前提是執(zhí)行了構(gòu)造器,所以構(gòu)造器的循環(huán)依賴沒法解決
getSingleton()從緩存里獲取單例對(duì)象步驟分析可知钳榨,Spring解決循環(huán)依賴的訣竅:就在于singletonFactories這個(gè)三級(jí)緩存舰罚。這個(gè)Cache里面都是ObjectFactory,它是解決問題的關(guān)鍵薛耻。
為什么要用三級(jí)緩存而不是二級(jí)緩存
可以看到三級(jí)緩存各自保存的對(duì)象营罢,這里重點(diǎn)關(guān)注二級(jí)緩存earlySingletonObjects和三級(jí)緩存singletonFactory,一級(jí)緩存可以進(jìn)行忽略饼齿。前面我們講過先實(shí)例化的bean會(huì)通過ObjectFactory半成品提前暴露在三級(jí)緩存中
所以如果沒有AOP的話確實(shí)可以兩級(jí)緩存就可以解決循環(huán)依賴的問題饲漾,如果加上AOP瘟滨,兩級(jí)緩存是無法解決的,不可能每次執(zhí)行singleFactory.getObject()方法都給我產(chǎn)生一個(gè)新的代理對(duì)象能颁,所以還要借助另外一個(gè)緩存來保存產(chǎn)生的代理對(duì)象
靜態(tài)代理
靜態(tài)代理的特點(diǎn)是, 為每一個(gè)業(yè)務(wù)增強(qiáng)都提供一個(gè)代理類, 由代理類來創(chuàng)建代理對(duì)象. 下面我們通過靜態(tà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è)代理類, 由代理類來創(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 包下的代理類來實(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)檫@里沒有配置切入點(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è)沒有接口的類該怎么辦呢? 這時(shí)我們可以使用CGLIB動(dòng)態(tài)代理. CGLIB動(dòng)態(tài)代理的原理是生成目標(biāo)類的子類, 這個(gè)子類對(duì)象就是代理對(duì)象, 代理對(duì)象是被增強(qiáng)過的.
注意: 不管有沒有接口都可以使用CGLIB動(dòng)態(tài)代理, 而不是只有在無接口的情況下才能使用.
(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)檫@里沒有配置切入點(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