<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
①構造器的迴圈依賴:
②單例模式下的setter迴圈依賴:
③非單例迴圈依賴:
if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName);}
原因很好理解,建立新的A時,發現要注入原型欄位B,又建立新的B發現要注入原型欄位A…這就套娃了, 你猜是先StackOverflow還是OutOfMemory?
Spring怕你不好猜,就先丟擲了BeanCurrentlyInCreationException
出現的背景:
首先,Spring內部維護了三個Map,也就是我們通常說的三級快取。
筆者翻閱Spring檔案倒是沒有找到三級快取的概念,可能也是本土為了方便理解的詞彙。
在Spring的DefaultSingletonBeanRegistry類中,你會赫然發現類上方掛著這三個Map:
singletonObjects
(一級快取)它是我們最熟悉的朋友,俗稱“單例池”“容器”,快取建立完成單例Bean的地方。earlySingletonObjects
(二級快取)對映Bean的早期參照,也就是說在這個Map裡的Bean不是完整的,甚至還不能稱之為“Bean”,只是一個Instance.singletonFactories
(三級快取) 對映建立Bean的原始工廠後兩個Map其實是“墊腳石”級別的,只是建立Bean的時候,用來藉助了一下,建立完成就清掉了。
那麼Spring 是如何通過上面介紹的三級快取來解決迴圈依賴的呢?
這裡只用 A,B 形成的迴圈依賴來舉例:
我們從原始碼的角度來看一下這個過程:
建立 Bean 的方法在 AbstractAutowireCapableBeanFactory::doCreateBean()
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; if (instanceWrapper == null) { // ① 範例化物件 instanceWrapper = this.createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null; Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null; // ② 判斷是否允許提前暴露物件,如果允許,則直接新增一個 ObjectFactory 到三級快取 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 新增三級快取的方法詳情在下方 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // ③ 填充屬性 this.populateBean(beanName, mbd, instanceWrapper); // ④ 執行初始化方法,並建立代理 exposedObject = initializeBean(beanName, exposedObject, mbd); return exposedObject; }
新增三級快取的方法如下:
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); } } } @FunctionalInterface public interface ObjectFactory<T> { T getObject() throws BeansException; }
通過這段程式碼,我們可以知道 Spring 在範例化物件的之後,就會為其建立一個 Bean 工廠,並將此工廠加入到三級快取中。
因此,Spring 一開始提前暴露的並不是範例化的 Bean,而是將 Bean 包裝起來的 ObjectFactory。為什麼要這麼做呢?
這實際上涉及到 AOP,如果建立的 Bean 是有代理的,那麼注入的就應該是代理 Bean,而不是原始的 Bean。但是 Spring 一開始並不知道 Bean 是否會有迴圈依賴,通常情況下(沒有迴圈依賴的情況下),Spring 都會在完成填充屬性,並且執行完初始化方法之後再為其建立代理。但是,如果出現了迴圈依賴的話,Spring 就不得不為其提前建立代理物件,否則注入的就是一個原始物件,而不是代理物件。因此,這裡就涉及到應該在哪裡提前建立代理物件?
Spring 的做法就是在 ObjectFactory 中去提前建立代理物件。它會執行 getObject() 方法來獲取到 Bean。實際上,它真正執行的方法如下:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof SmartInstantiationAwareBeanPostProcessor) { SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp; // 如果需要代理,這裡會返回代理物件;否則返回原始物件 exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }
因為提前進行了代理,避免對後面重複建立代理物件,會在 earlyProxyReferences 中記錄已被代理的物件。
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { @Override public Object getEarlyBeanReference(Object bean, String beanName) { Object cacheKey = getCacheKey(bean.getClass(), beanName); // 記錄已被代理的物件 this.earlyProxyReferences.put(cacheKey, bean); return wrapIfNecessary(bean, beanName, cacheKey); } }
通過上面的解析,我們可以知道 Spring 需要三級快取的目的是為了在沒有迴圈依賴的情況下,延遲代理物件的建立,使 Bean 的建立符合 Spring 的設計原則。
我們目前已經知道了 Spring 的三級依賴的作用,但是 Spring 在注入屬性的時候是如何去獲取依賴的呢?
他是通過一個getSingleton()方法去獲取所需要的 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) { // Bean 工廠中獲取 Bean singletonObject = singletonFactory.getObject(); // 放入到二級快取中 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
當 Spring 為某個 Bean 填充屬性的時候,它首先會尋找需要注入物件的名稱,然後依次執行 getSingleton() 方法得到所需注入的物件,而獲取物件的過程就是先從一級快取中獲取,一級快取中沒有就從二級快取中獲取,二級快取中沒有就從三級快取中獲取,如果三級快取中也沒有,那麼就會去執行 doCreateBean() 方法建立這個 Bean。
流程圖總結:
我們現在已經知道,第三級快取的目的是為了延遲代理物件的建立,因為如果沒有依賴迴圈的話,那麼就不需要為其提前建立代理,可以將它延遲到初始化完成之後再建立。
既然目的只是延遲的話,那麼我們是不是可以不延遲建立,而是在範例化完成之後,就為其建立代理物件,這樣我們就不需要第三級快取了。因此,我們可以將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)) { // 判斷一級快取中不存在此物件 object o = singletonFactory.getObject(); // 直接從工廠中獲取 Bean this.earlySingletonObjects.put(beanName, o); // 新增至二級快取中 this.registeredSingletons.add(beanName); } } }
這樣的話,每次範例化完 Bean 之後就直接去建立代理物件,並新增到二級快取中。測試結果是完全正常的,Spring 的初始化時間應該也是不會有太大的影響,因為如果 Bean 本身不需要代理的話,是直接返回原始 Bean 的,並不需要走複雜的建立代理 Bean 的流程。
測試證明,二級快取也是可以解決迴圈依賴的。為什麼 Spring 不選擇二級快取,而要額外多新增一層快取呢?
如果 Spring 選擇二級快取來解決迴圈依賴的話,那麼就意味著所有 Bean 都需要在範例化完成之後就立馬為其建立代理,而Spring 的設計原則是在 Bean 初始化完成之後才為其建立代理。所以,Spring 選擇了三級快取。但是因為迴圈依賴的出現,導致了 Spring 不得不提前去建立代理,因為如果不提前建立代理物件,那麼注入的就是原始物件,這樣就會產生錯誤。
如下controller為主bean,service為所依賴的bean:
@RestController public class AccountController { private static final Logger LOG = LoggerFactory.getLogger(AccountController.class); private AccountService accountService; // 建構函式依賴注入 // 不管是否設定為required為true,都會出現迴圈依賴問題 @Autowire // @Autowired(required = false) public AccountController(AccountService accountService) { this.accountService = accountService; } } @Service public class AccountService { private static final Logger LOG = LoggerFactory.getLogger(AccountService.class); // 屬性值依賴注入 @Autowired private AccountController accountController; }
啟動列印如下:
***************************
APPLICATION FAILED TO START
***************************Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| accountController defined in file [/Users/xieyizun/study/personal-projects/easy-web/target/classes/com/yzxie/easy/log/web/controller/AccountController.class]
↑ ↓
| accountService (field private com.yzxie.easy.log.web.controller.AccountController com.yzxie.easy.log.web.service.AccountService.accountController)
└─────┘
如果是在主bean中通過屬性值或者setter方法注入所依賴的bean,而在所依賴的bean使用了建構函式注入主bean物件,這種情況則不會出現迴圈依賴問題。
@RestController public class AccountController { private static final Logger LOG = LoggerFactory.getLogger(AccountController.class); // 屬性值注入 @Autowired private AccountService accountService; } @Service public class AccountService { private AccountController accountController; // 建構函式注入 @Autowired public AccountService(AccountController accountController) { this.accountController = accountController; } }
原因主要是主bean物件通過建構函式注入所依賴bean物件時,無法建立該所依賴的bean物件,獲取該所依賴bean物件的參照。因為如下程式碼所示。
建立主bean物件,呼叫順序為:
// bean物件範例建立的核心實現方法 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // 省略其他程式碼 // 1. 呼叫建構函式建立該bean物件,若不存在建構函式注入,順利通過 instanceWrapper = createBeanInstance(beanName, mbd, args); // 2. 在singletonFactories快取中,放入該bean物件,以便解決迴圈依賴問題 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); // 3. populateBean方法:bean物件的屬性賦值 populateBean(beanName, mbd, instanceWrapper); // 省略其他程式碼 return exposedObject; }
createBeanInstance是呼叫建構函式建立主bean物件,在裡面會注入建構函式中所依賴的bean,而此時並沒有執行到addSingletonFactory方法來新增主bean物件的建立工廠到三級快取singletonFactories中。故在createBeanInstance內部,注入和建立該主bean物件時,如果在建構函式中存在對其他bean物件的依賴,並且該bean物件也存在對主bean物件的依賴,則會出現迴圈依賴問題,原理如下:
主bean物件為A,A物件依賴於B物件,B物件也存在對A物件的依賴,建立A物件時,會觸發B物件的建立,則B無法通過三級快取機制獲取主bean物件A的參照(即B如果通過建構函式注入A,則無法建立B物件;如果通過屬性注入或者setter方法注入A,則建立B物件後,對B物件進行屬性賦值,會卡在populateBean方法也無法返回)。 故無法建立主bean物件所依賴的B,建立主bean物件A時,createBeanInstance方法無法返回,出現程式碼死鎖,程式報迴圈依賴錯誤。
注意:spring的迴圈依賴其實是可以關閉的,設定allowCircularReference=false
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45