Spring如何解決循環依賴?
springboot實戰電商項目mall4j (https://gitee.com/gz-yami/mall4j)
java開源商城系統
@component
class A {
private B b;
}
@component
class B {
private A a;
}
類A依賴了B作為屬性,類B又使用類A作為屬性,彼此循環依賴。
源碼理解:
//調用AbstractBeanFactory.doGetBean(),向IOC容器獲取Bean,觸發依賴注入的方法
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
...
// 第一次getSingleton獲取對象實例
// 先從緩存中取是否已經有被創建過的單例類型的Bean[沒有的話就去獲取半成品的,也就是earlySingletonObjects,緩存二的東西]
// 對於單例模式的Bean整個IOC容器中只創建一次,不需要重複創建
Object sharedInstance = getSingleton(beanName);
...
try {
//創建單例模式Bean的實例對象
if (mbd.isSingleton()) {
//第二次getSingleton嘗試創建目標對象,並且注入屬性
//這裏使用了一個匿名內部類,創建Bean實例對象,並且註冊給所依賴的對象
sharedInstance = getSingleton(beanName, () -> {
try {
//創建一個指定Bean實例對象,如果有父級繼承,則合併子類和父類的定義
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
//顯式地從容器單例模式Bean緩存中清除實例對象
destroySingleton(beanName);
throw ex;
}
});
// 如果傳入的是factoryBean,則會調用其getObject方法,得到目標對象
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
//IOC容器創建原型模式Bean實例對象
}
...
} catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
...
return (T) bean;
}
也就是實例化A的時候在緩存中沒找到[第一個getSingleton],就去第二個getSingleton實例化A[實際上是調用了doCreateBean()],由於A需要B,又去doGetBean嘗試獲取B,發現B也不在緩存中,繼續調用第二個getSingleton去實例化,當要注入屬性A的時候在二級緩存找到了半成品A,成功注入返回到A實例化的階段,將B注入。
第一個getSingleton代碼
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//從spring容器中獲取bean
Object singletonObject = this.singletonObjects.get(beanName);//緩存1
//如果獲取不到 判斷要獲取的對象是不是正在創建過程中----如果是,則去緩存(三級緩存)中取對象(不是bean)
//isSingletonCurrentlyInCreation() 存放的對象 的時機是在getBean中第二次調用getSingleton時候beforeSingletonCreation(beanName);存進去的
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);//緩存2
if (singletonObject == null && allowEarlyReference) {//allowEarlyReference--判斷是否支持循環依賴,默認為true
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);//緩存3
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//將三級緩存升級為二級緩存
this.earlySingletonObjects.put(beanName, singletonObject);
//從三級緩存中刪除 為什麼刪除?防止重複創建。設置三級緩存的目的是為了提高性能,因為每次創建都需要經過factory,會花費很多時間
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
在實例化AB的時候,三個緩存都是找不到這兩個類的,因為兩者均未創建;
三級緩存
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);//一級緩存,存放完整的bean信息
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);//三級緩存,bean創建完了就放進去,還有他的bean工廠
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);//二級緩存,存放還沒進行屬性賦值的bean對象,也即半成品bean
第二個getSingleton代碼
此處將A先創建好<u>放入三級緩存</u>中,實際上是委託給另一個doGetBean()完成的
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
...
synchronized (this.singletonObjects) {
// 再次判斷ioc容器中有無該bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
..before..
try {
// 回調到doCreateBean
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
..after..
}
return singletonObject;
}
}
由於第一個獲取單例的方法找不到AB,故此將會進入第二個獲取單例的方法試圖找到,這個方法裏singletonFactory.getObject()為核心,將會回調到doCreateBean方法繼續創建Bean。
doCreateBean代碼
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
//封裝被創建的Bean對象
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
//單例的情況下嘗試從factoryBeanInstanceCache獲取 instanceWrapper,並清除同名緩存
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
// 創建Bean實例
// instanceWrapper會包裝好目標對象
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
...
//向容器中緩存單例模式的Bean對象,以防循環引用,allowCircularReferences是判斷是否支持循環依賴,這個值可以改為false
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
//判斷是否允許循環依賴
if (earlySingletonExposure) {
...
//將這個對象的工廠放入緩存中 (註冊bean工廠singletonFactories,第一個getSingleton使用的),此時這個bean還沒做屬性注入
//這裏是一個匿名內部類,為了防止循環引用,儘早持有對象的引用
// getEarlyBeanReference很特別,這裏面會做aop代理
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
//Bean對象的初始化,依賴注入在此觸發
//這個exposedObject在初始化完成之後返回作為依賴注入完成後的Bean
Object exposedObject = bean;
try {
//填充屬性 -- 自動注入
//前面是實例化,並沒有設置值,這裏是設置值.將Bean實例對象封裝,並且Bean定義中配置的屬性值賦值給實例對象
//這裏做注入的時候會判斷依賴的屬性在不在,不在就調用doGetBean繼續創建
populateBean(beanName, mbd, instanceWrapper);
// 該方法主要是對bean做一些擴展
// 初始化Bean對象。屬性注入已完成,處理各種回調
// (對實現Aware接口(BeanNameAware、BeanClassLoaderAware、BeanFactoryAware)的bean執行回調、
// aop、init-method、destroy-method?、InitializingBean、DisposableBean等)
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
return exposedObject;
}
在這個方法裏,實例化A的時候,已經把A作為一個半成品通過調用addSingletonFactory方法將其加入了三級緩存singletonFactories,方便在遞歸實例化B的時候可以獲取到A的半成品實例,詳細代碼如下:
將創建的bean加入<u>三級緩存</u>
發生在addSingletonFactory這個方法
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
//放到三級緩存
// 註冊bean工廠
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
那麼是在什麼時候會發生一個遞歸的調用呢?
實際上是在<u>populateBean(beanName, mbd, instanceWrapper);</u>要做屬性注入的時候,假設是根據名稱自動注入的,調用<u>autowireByName(),</u>該法會去循環遍歷在getBean之前已經把xml文件的屬性加入到註冊表之類的屬性,
populateBean有一個autowireByName的方法,代碼如下
protected void autowireByName(
String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
//對Bean對象中非簡單屬性(不是簡單繼承的對象,如8中原始類型,字符串,URL等都是簡單屬性)進行處理
String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
for (String propertyName : propertyNames) {
//如果Spring IOC容器中包含指定名稱的Bean,就算還沒進行初始化,A發現有B屬性時,B屬性已經被寫入註冊表之類的東西,所以這個判斷返回true
if (containsBean(propertyName)) {
//調用getBean方法向IOC容器索取指定名稱的Bean實例,迭代觸發屬性的初始化和依賴注入
Object bean = getBean(propertyName); //實際上就是委託doGetBean()
pvs.add(propertyName, bean);
//指定名稱屬性註冊依賴Bean名稱,進行屬性依賴注入
registerDependentBean(propertyName, beanName);
if (logger.isDebugEnabled()) {
logger.debug("Added autowiring by name from bean name '" + beanName +
"' via property '" + propertyName + "' to bean named '" + propertyName + "'");
}
}
...
}
}
Object bean = getBean(propertyName); 這個方法實際上又去委託了doGetBean(),又一次遞歸的走上了流程,也就是A在實例化到該步時發現,還有一個B,就會又從doGetBean()開始,一步步的尋找創建,不同的是,當B走到這個根據名稱注入的方法時,此時的已經能在二級緩存裏找到A的身影了,無需再次創建A對象。
總結
spring運用三級緩存解決了循環依賴的問題;
採用遞歸的方式,逐步的去實例化對象,並將上一步已經加入緩存的半成品對象作為屬性注入;
等到走到最後一個遞歸時,將會逐步返回,把對應的實例一個個創建好。
springboot實戰電商項目mall4j (https://gitee.com/gz-yami/mall4j)
java開源商城系統