@ "TOC" 前言 關於Spring 迴圈引用 網上的分析文章很多,寫的水平良莠不齊,雖然看完了 知道怎麼個回事 但是過段時間還是忘記了,主要本人沒過目不忘的本領哈,但是只要記住主要的點就好了 但是如果你自己想更深入的瞭解,還是要自己去看源碼分析一波,因為別人分析的時候,有些知識點你是get不到的 ...
@目錄
前言
關於Spring 迴圈引用 網上的分析文章很多,寫的水平良莠不齊,雖然看完了 知道怎麼個回事 但是過段時間還是忘記了,主要本人沒過目不忘的本領哈,但是只要記住主要的點就好了
但是如果你自己想更深入的瞭解,還是要自己去看源碼分析一波,因為別人分析的時候,有些知識點你是get不到的,只有當自己走進源碼去看的時候,才有get到更多的!比如網上很多文章都分析Springs是怎麼解決迴圈依賴的 但是為什麼只有單類的才可以,Prototype的就不行呢,在哪裡不行,或者說構造器的註入為什麼也不可以,最後如果解決迴圈依賴,或者說 怎麼去換中寫法去解決問題。
紙上得來終覺淺 絕知此事要躬行! 這句話獻給正在讀文章的你,看完記得點贊,還有就是自己去下載Spring 源碼 去看看
正文
OK,進入正文,當然上面也不是廢話啦,Spring 的迴圈引用 我想讀者們應該知道,不知道的話,算了 來個code把!
@Component
public class CycleTestServiceA {
private CycleTestServiceB b;
public void setB(CycleTestServiceB b) {
this.b = b;
}
}
@Component
public class CycleTestServiceB {
private CycleTestServiceA a;
public void setA(CycleTestServiceA a) {
this.a = a;
}
}
上面的 代碼 就是一個普通的set註入的方式,A裡面依賴B,B裡面依賴A,這樣就導致了迴圈依賴,Component預設是Singleton的
分析
我們從Spring Beanc創建開始作為入口,在Spring IoC 容器中一個完整的Bean 要進過實例化 和初始化的階段
Spring Bean 實例化就getBean的過程
那我們接進入源碼去看下getBean的過程
doGetBean
getBean方法時 BeanFactory 介面的方法 他的實現類有很多,我們跟進去他的抽象實現類org/springframework/beans/factory/support/AbstractBeanFactory.java 類,其實都是調用了doGetBean方法
下麵是我截取的核心代碼
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
/*
* 檢測是否 有緩存對象 這個方法時處理迴圈依賴的關鍵入口
* 記住這個的代碼 我還會回來的
* */
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.debug("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
}
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
/*
*Prototype bean 是否在創建當中 如果存在 說明產生了迴圈依賴 處理Bean 迴圈依賴的地方
*這個地方就是為什麼Scope 是Prototype的時候 會報迴圈依賴的錯誤,慢慢看 後面會解釋
* */
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
...
if (!typeCheckOnly) {
markBeanAsCreated(beanName);//這個方法就是把當前的bean 加入到alreadyCreated的set集合中 後面有些判斷需要
}
try {
...
/*
* 獲取Bean 的依賴項 這邊的依賴 是我們在xml 有時候可以配置的depends-on的依賴 和我們本次講的迴圈依賴不是同一個
* 我特別說明下
* */
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
//註冊依賴 創建Bean 等
}
}
/*
* 如果是單列 創建createBean 記住這個的代碼 我還會回來的
* */
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
...
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
/*
* Prototype對象
* */
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
/*
* 不是Singleton也不是Prototype,可能是自定義scope的對象
* */
else {
...
}
}
}
...
return (T) bean;
}
上面是dogetBean()的核心方法
為什麼Prototype不可以
帶著這個問題 我們可以從上面的代碼中 看下 Spring在處理麽Prototype的時候 有2個方法beforePrototypeCreation(),afterPrototypeCreation(),
上下代碼
/** Names of beans that are currently in creation */
private final ThreadLocal<Object> prototypesCurrentlyInCreation =
new NamedThreadLocal<Object>("Prototype beans currently in creation");
protected void beforePrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal == null) {
this.prototypesCurrentlyInCreation.set(beanName);
}
else if (curVal instanceof String) {
Set<String> beanNameSet = new HashSet<String>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
}
else {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.add(beanName);
}
}
protected void afterPrototypeCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal instanceof String) {
this.prototypesCurrentlyInCreation.remove();
}
else if (curVal instanceof Set) {
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.remove(beanName);
if (beanNameSet.isEmpty()) {
this.prototypesCurrentlyInCreation.remove();
}
}
}
上面的代碼 我相信小伙伴都能看的懂,就是用一個set集合存儲當前正在創建的Bean的BeanName,而且是用ThreadLocal去存儲Set集合的 ThreadLocal是每個線程私有的。看到這個 我們再把目光往代碼上面看一看 isPrototypeCurrentlyInCreation這個方法的判斷
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
看到了麽 這邊就是用這個ThreadLocal裡面的set集合去判斷的,為什麼用ThreadLocal想下,你想呀,A依賴B,而B依賴A,AB都是Prototype的,A創建的時候 A會加入到這個set集合中,然後A去填充實例的時候,因為要依賴B,所以去getB,發現B又依賴A,這個時候有要getA,你看 當執行到 最上面的判斷isPrototypeCurrentlyInCreation的時候,是不報了迴圈引用的錯,因為A已經在prototypesCurrentlyInCreation的Set集合中了,因為整個流程一定是一個線程走下去的,所以存入ThreadLocal中,一點問題沒有,而且還不受其他線程影響~
createBean
不管是哪種Scope 都是要調用createBean方法的,我們跟進去代碼 發現唯一重寫的實現在org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java 中
我們進入代碼看下
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
RootBeanDefinition mbdToUse = mbd;
...
try {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
// 該函數的作用是給 BeanPostProcessors 後置處理器返回一個代理對象的機會
// 這裡是實現AOP處理的重要地方
// AOP是通過BeanPostProcessor機制實現的,而介面InstantiationAwareBeanPostProcessor是實現代理的重點
Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
if (bean != null) {
return bean;
}
}
...
/*
* 後置處理器 沒有返回有效的bean 就創建
* */
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isDebugEnabled()) {
logger.debug("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
這邊 我看到一句英文註釋,都沒捨得替換中文,Give BeanPostProcessors a chance to return a proxy instead of the target bean instance. 哈哈 給後置處理器一個返回代理bean的機會,這邊就是Spring 中實現AOP的重點,動態代理 其實就是使用後置處理器 替換了target Bean 的實例,從而達到代理的作用,這個以後聊到AOP 的時候在慢慢聊吧!這個最核心的代碼還在再doCreateBean中,繼續跟進
doCreateBean
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;//BeanWrapper 是Bean 的包裝類 方便對Bean 實例的操作
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
mbd.resolvedTargetType = beanType;
....
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));//滿足三個條件 單列 運行迴圈引用 bean 是否正在創建中
if (earlySingletonExposure) {
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);//提前暴露引用 獲取早期的引用
}
});
}
// Initialize the bean instance. 初始化Bean 實例
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);//填充Bean
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);//執行初始化Bean裡面的方法
}
}
...
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
/*
*這邊其實還是做了一個判斷,exposedObject是經過了 initializeBean方法方法的
*而bean還是那個提前暴露的Bean,
*為什麼要做這個判斷你,是因為exposedObject經過了initializeBean裡面的後置處理器的修改 可能Object 已經改變了
**/
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
/*
*有興趣的可以根據到上面的每一個方法看下 ,這邊就是判斷如果提前暴露的bean已經和在後置處理器裡面修改了並且不一樣了,就拋出異常,因為提前暴露的Bean 可能作為了另外的bean的依賴 這樣就會導致單類的bean在容器中有2個實例的出現,這是非法的!
*/
if (!actualDependentBeans.isEmpty()) {
//拋出一個異常 由於很多文字我就刪掉了
}
}
}
}
...
return exposedObject;
}
earlySingletonExposure這個主要關註的是earlySingletonExposure 這邊的代碼,這個就是在Bean 實例化完成後,開始填充屬性之間發的代碼
earlySingletonExposure為true 要滿足三個條件
- 單類
- 允許迴圈引用
- 當時單類正在創建中
前面2個可以理解 那最後一個又是什麼呢?話不多說 進入方法看下
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}
這個方法 一看很簡答 就是判斷當前的bean是否在singletonsCurrentlyInCreation的set集合中 那這個集合又是什麼時候加入的呢?帶著這個想法 我又重頭掃描了一篇代碼 還記的org/springframework/beans/factory/support/AbstractBeanFactory.java代碼中的doGetBean()方法裡面Singleton的Bean 在創建Instance的時候是調用了getSingleton方法麽,不清楚的話 可以往上看下
getEarlyBeanReference
這個方法 是 addSingletonFactory 方法 構建ObjectFactory的參數的時候 裡面返回使用方法
看下代碼:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
if (exposedObject == null) {
return null;
}
}
}
}
return exposedObject;
}
裡面最主要的就是看下getEarlyBeanReference方法 這個方法時SmartInstantiationAwareBeanPostProcessor裡面的方法,他的實現有2個 一個是org/springframework/beans/factory/config/InstantiationAwareBeanPostProcessorAdapter.java 還有一個是動態代理使用的 我就不列舉了,
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}
看了下 其實InstantiationAwareBeanPostProcessorAdapter的重寫就是 返回了當前的bean 沒有做任何操作。這邊其實就是做了一個引用的保存。
getSingleton
代碼位於org/springframework/beans/factory/support/DefaultListableBeanFactory.java中
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
...
beforeSingletonCreation(beanName);
boolean newSingleton = false;
...
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
finally {
afterSingletonCreation(beanName);
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
這個方法其所就是SingletonBean的核心創建流程
beforeSingletonCreation
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
當我看到這個singletonsCurrentlyInCreation.add的時候 我很欣慰 因為我之前的問題解決了 就是這個方法把Bean 放入到之前的singletonsCurrentlyInCreation的集合中的
singletonFactory.getObject
這個應該都很清楚了 就是我們方法傳入的匿名的ObjectFactory對象,當之前getObject的時候 才會執行我們剛纔的看的createBean方法
afterSingletonCreation
protected void afterSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
}
}
看下afterSingletonCreation方法裡面的東西也很簡單,就是從singletonsCurrentlyInCreation集合中移除
addSingleton
先看下代碼
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
看到 這邊 就不得表介紹下三級緩存了
- singletonObjects 實例化 初始化都完成的bean 緩存
- earlySingletonObjects 可提前引用的 Bean 緩存,這裡面的Bean 是一個非完整的bean,屬性填充 後置處理器都未執行的bean
- singletonFactories 單類bean的創建工廠函數對象
說道這裡 我們就清楚了 這個方法其所就是在二級緩存和三級緩存中刪除當前的Bean,把當前的Bean 放入到一級緩存中,因為到了這一步 bean 的實例化,屬性填充,後置處理器執行,初始化等方法都已經執行了。
addSingletonFactory
這個方法 哪裡用的呢 那我們有要回到上面的代碼doCreateBean中 當earlySingletonExposure為true的時候 會調用這個方法addSingletonFactory
這個方法就是 當前的Bean可以提前引用的話執行的方法
看下代碼也很簡答
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
看下這個方法 說白了就是往三級緩存裡面存放bean的ObjectFactory對象 這個地方也是處理迴圈引用的關鍵,這個時候Bean 剛剛進行了實例化 還沒有進行bean的屬性填充和初始化等一些列方法
那怎麼去解決提前引用的呢?可以看下ObjectFactory返回的是getEarlyBeanReference對象
getSingleton(beanName)
這個方法是在doGetBean方法中 從緩存中獲取Bean 對象的方法 這個方法很關鍵 是處理迴圈依賴的入口,那我們跟進去看下方法
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 != NULL_OBJECT ? singletonObject : null);
}
最終調用的方法如上,allowEarlyReference是為true的,我們還是用最上面的ServiceA和ServiceB 為例,第一次ServiceA 進入的時候 是沒法進入下麵的判斷的 應為當前ServiceA不在SingletonCurrentlyInCreation中,但是當第二次進來,第二次是什麼時候呢,就是在填充ServiceB的時候 需要依賴 ServiceB,這個時候ServiceB也要執行getBean的流程,發現又依賴ServiceA,這個時候 ServiceA就是在SingletonCurrentlyInCreation的集合中了,而且在三級緩存中,這個時候會進行判斷條件裡面的方法,先找一級緩存,找不到就找二級緩存,最後找三級緩存,然後將取出三級緩存裡面的ObjectFactory執行getObject方法 就是獲取我們上面提到的提前引用的bean,最後將bean 放入到二級緩存,從三級緩存中移除~
核心說明
看完了 上面的一推 也許很懵逼,可能也是我文字組織能力差,只能以後慢慢改變
緩存的說明
上面涉及到幾個緩存 我在邊在重寫描述一下
名稱 | 類型 | 使用說明 | 所屬類 |
---|---|---|---|
singletonObjects | Map<String, Object> | 實例化 初始化都完成的bean的緩存 | DefaultSingletonBeanRegistry.java |
earlySingletonObjects | Map<String, Object> | 可提前引用的 Bean 緩存,這裡面的Bean 是一個非完整的bean,屬性填充 後置處理器都未執行的bean | DefaultSingletonBeanRegistry.java |
singletonFactories | Map<String, ObjectFactory<?>> | 單類bean的創建工廠函數對象 | DefaultSingletonBeanRegistry.java |
singletonsCurrentlyInCreation | Set |
馬上要創建的單類Bean的集合 | DefaultSingletonBeanRegistry.java |
prototypesCurrentlyInCreation | ThreadLocal | object 是一個Set |
AbstractBeanFactory.java |
alreadyCreated | Set |
至少創建過一次的Bean 在提前暴露的bean修改了導致不一致時 判斷會用到 | AbstractBeanFactory.java |
執行流程圖
最終我還是用一個方法執行的流程圖 來描述下 迴圈依賴的處理
構造器的註入解決
那麼為什麼構造器的註入方式不行呢?原因是因為 Bean在實例化階段的時候createBeanInstance的時候就會去創建依賴的B,這樣的話A根本就走不到提前暴露的代碼塊,所以會報一個迴圈引用的錯誤,報錯的地方就是構造函數參數bean 創建的地方,自己可以寫個demo,調試下 在哪一步報錯,博主可是看了半天 才找到,哈哈!
解決方法
關於如果解決構造器的迴圈註入
https://www.baeldung.com/circular-dependencies-in-spring
這是一篇外國博文,小伙伴們可以看下
- 使用懶載入
- 修改使用setter註入的方式
- 使用PostConstruct註解
- InitializingBean 後置處理器的方式
總結
Spring 處理迴圈依賴的核心就是 三級緩存,讓Bean 提前暴露出來,可以提前引用,讓互相依賴的Bean 可以流程上執行下去,從而解決了迴圈依賴的問題
最後的最後 還是自己對照源碼 自己理解一遍,我相信一定會加深你的理解,一定會有收穫
碼字不易,花了一個周末的時間,各位看官喜歡的話點個贊,鼓勵下博主,繼續創造,多謝~