spring深入學習(十七) IOC 之分析各 scope 的 bean 建立
在 Spring 中存在著不同的 scope,預設是 singleton ,還有 prototype、request 等等其他的 scope,他們的初始化步驟是怎樣的呢?這個答案在這篇部落格中給出。
singleton
Spring 的 scope 預設為 singleton,其初始化的程式碼如下:
if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }
第一部分分析了從快取中獲取單例模式的 bean,但是如果快取中不存在呢?則需要從頭開始載入 bean,這個過程由 getSingleton()
實現。
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); // 全域性加鎖 synchronized (this.singletonObjects) { // 從快取中檢查一遍 // 因為 singleton 模式其實就是複用已經建立的 bean 所以這步驟必須檢查 Object singletonObject = this.singletonObjects.get(beanName); // 為空,開始載入過程 if (singletonObject == null) { // 省略 部分程式碼 // 載入前置處理 beforeSingletonCreation(beanName); boolean newSingleton = false; // 省略程式碼 try { // 初始化 bean // 這個過程其實是呼叫 createBean() 方法 singletonObject = singletonFactory.getObject(); newSingleton = true; } // 省略 catch 部分 } finally { // 後置處理 afterSingletonCreation(beanName); } // 加入快取中 if (newSingleton) { addSingleton(beanName, singletonObject); } } // 直接返回 return singletonObject; } }
其實這個過程並沒有真正建立 bean,僅僅只是做了一部分準備和預處理步驟,真正獲取單例 bean 的方法其實是由 singletonFactory.getObject()
這部分實現,而 singletonFactory 由回撥方法產生。那麼這個方法做了哪些準備呢?
- 再次檢查快取是否已經載入過,如果已經載入了則直接返回,否則開始載入過程。
- 呼叫
beforeSingletonCreation()
記錄載入單例 bean 之前的載入狀態,即前置處理。 - 呼叫引數傳遞的 ObjectFactory 的
getObject()
- 呼叫
afterSingletonCreation()
進行載入單例後的後置處理。 - 將結果記錄並加入值快取中,同時刪除載入 bean 過程中所記錄的一些輔助狀態。
流程中涉及的三個方法 beforeSingletonCreation()
與 afterSingletonCreation()
在部落格 【死磕 Spring】—– 載入 bean 之 快取中獲取單例 bean
中分析過了,所以這裡不再闡述了,我們看另外一個方法 addSingleton()
。
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
一個 put、一個 add、兩個 remove。singletonObjects 單例 bean 的快取,singletonFactories 單例 bean Factory 的快取,earlySingletonObjects “早期”建立的單例 bean 的快取,registeredSingletons 已經註冊的單例快取。
載入了單例 bean 後,呼叫 getObjectForBeanInstance()
從 bean 例項中獲取物件。該方法已經在 【死磕 Spring】—– 載入 bean 之 快取中獲取單例 bean 詳細分析了。
原型模式
else if (mbd.isPrototype()) {
Object prototypeInstance = null;
try {
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
原型模式的初始化過程很簡單:直接建立一個新的例項就可以了。過程如下:
- 呼叫
beforeSingletonCreation()
記錄載入原型模式 bean 之前的載入狀態,即前置處理。 - 呼叫
createBean()
建立一個 bean 例項物件。 - 呼叫
afterSingletonCreation()
進行載入原型模式 bean 後的後置處理。 - 呼叫
getObjectForBeanInstance()
從 bean 例項中獲取物件。
其他作用域
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; consider " +
"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
核心流程和原型模式一樣,只不過獲取 bean 例項是由 scope.get()
實現,如下:
public Object get(String name, ObjectFactory<?> objectFactory) {
// 獲取 scope 快取
Map<String, Object> scope = this.threadScope.get();
Object scopedObject = scope.get(name);
if (scopedObject == null) {
scopedObject = objectFactory.getObject();
// 加入快取
scope.put(name, scopedObject);
}
return scopedObject;
}
對於上面三個模組,其中最重要的有兩個方法,一個是 createBean()
、一個是 getObjectForBeanInstance()
。這兩個方法在上面三個模組都有呼叫,createBean()
後續詳細說明,getObjectForBeanInstance()
在部落格 【死磕 Spring】—– 載入 bean 之 快取中獲取單例 bean 中有詳細講解,這裡再次闡述下(此段內容來自《Spring 原始碼深度解析》):這個方法主要是驗證以下我們得到的 bean 的正確性,其實就是檢測當前 bean 是否是 FactoryBean 型別的 bean,如果是,那麼需要呼叫該 bean 對應的 FactoryBean 例項的 getObject()
作為返回值。無論是從快取中獲得到的 bean 還是通過不同的 scope 策略載入的 bean 都只是最原始的 bean 狀態,並不一定就是我們最終想要的 bean。舉個例子,加入我們需要對工廠 bean 進行處理,那麼這裡得到的其實是工廠 bean 的初始狀態,但是我們真正需要的是工廠 bean 中定義 factory-method 方法中返回的 bean,而 getObjectForBeanInstance()
就是完成這個工作的。