天天看點

【死磕 Spring】----- IOC 之分析各 scope 的 bean 建立

在 Spring 中存在着不同的 scope,預設是 singleton ,還有 prototype、request 等等其他的 scope,他們的初始化步驟是怎樣的呢?這個答案在這篇部落格中給出。

singleton

Spring 的 scope 預設為 singleton,其初始化的代碼如下:

  1. if (mbd.isSingleton()) {

  2. sharedInstance = getSingleton(beanName, () -> {

  3. try {

  4. return createBean(beanName, mbd, args);

  5. }

  6. catch (BeansException ex) {

  7. destroySingleton(beanName);

  8. throw ex;

  9. }

  10. });

  11. bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

  12. }

第一部分分析了從緩存中擷取單例模式的 bean,但是如果緩存中不存在呢?則需要從頭開始加載 bean,這個過程由

getSingleton()

實作。

  1. public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {

  2. Assert.notNull(beanName, "Bean name must not be null");

  3. // 全局加鎖

  4. synchronized (this.singletonObjects) {

  5. // 從緩存中檢查一遍

  6. // 因為 singleton 模式其實就是複用已經建立的 bean 是以這步驟必須檢查

  7. Object singletonObject = this.singletonObjects.get(beanName);

  8. // 為空,開始加載過程

  9. if (singletonObject == null) {

  10. // 省略 部分代碼

  11. // 加載前置處理

  12. beforeSingletonCreation(beanName);

  13. boolean newSingleton = false;

  14. // 省略代碼

  15. try {

  16. // 初始化 bean

  17. // 這個過程其實是調用 createBean() 方法

  18. singletonObject = singletonFactory.getObject();

  19. newSingleton = true;

  20. }

  21. // 省略 catch 部分

  22. }

  23. finally {

  24. // 後置處理

  25. afterSingletonCreation(beanName);

  26. }

  27. // 加入緩存中

  28. if (newSingleton) {

  29. addSingleton(beanName, singletonObject);

  30. }

  31. }

  32. // 直接傳回

  33. return singletonObject;

  34. }

  35. }

其實這個過程并沒有真正建立 bean,僅僅隻是做了一部分準備和預處理步驟,真正擷取單例 bean 的方法其實是由

singletonFactory.getObject()

這部分實作,而 singletonFactory 由回調方法産生。那麼這個方法做了哪些準備呢?

  1. 再次檢查緩存是否已經加載過,如果已經加載了則直接傳回,否則開始加載過程。
  2. 調用

    beforeSingletonCreation()

    記錄加載單例 bean 之前的加載狀态,即前置處理。
  3. 調用參數傳遞的 ObjectFactory 的

    getObject()

    執行個體化 bean。
  4. afterSingletonCreation()

    進行加載單例後的後置處理。
  5. 将結果記錄并加入值緩存中,同時删除加載 bean 過程中所記錄的一些輔助狀态。

流程中涉及的三個方法

beforeSingletonCreation()

afterSingletonCreation()

在部落格 【死磕 Spring】----- 加載 bean 之 緩存中擷取單例 bean 中分析過了,是以這裡不再闡述了,我們看另外一個方法

addSingleton()

  1. protected void addSingleton(String beanName, Object singletonObject) {

  2. synchronized (this.singletonObjects) {

  3. this.singletonObjects.put(beanName, singletonObject);

  4. this.singletonFactories.remove(beanName);

  5. this.earlySingletonObjects.remove(beanName);

  6. this.registeredSingletons.add(beanName);

  7. }

  8. }

一個 put、一個 add、兩個 remove。singletonObjects 單例 bean 的緩存,singletonFactories 單例 bean Factory 的緩存,earlySingletonObjects “早期”建立的單例 bean 的緩存,registeredSingletons 已經注冊的單例緩存。

加載了單例 bean 後,調用

getObjectForBeanInstance()

從 bean 執行個體中擷取對象。該方法已經在 【死磕 Spring】----- 加載 bean 之 緩存中擷取單例 bean 詳細分析了。

原型模式

  1. else if (mbd.isPrototype()) {

  2. Object prototypeInstance = null;

  3. try {

  4. beforePrototypeCreation(beanName);

  5. prototypeInstance = createBean(beanName, mbd, args);

  6. }

  7. finally {

  8. afterPrototypeCreation(beanName);

  9. }

  10. bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);

  11. }

原型模式的初始化過程很簡單:直接建立一個新的執行個體就可以了。過程如下:

  1. beforeSingletonCreation()

    記錄加載原型模式 bean 之前的加載狀态,即前置處理。
  2. createBean()

    建立一個 bean 執行個體對象。
  3. afterSingletonCreation()

    進行加載原型模式 bean 後的後置處理。
  4. getObjectForBeanInstance()

    從 bean 執行個體中擷取對象。

其他作用域

  1. String scopeName = mbd.getScope();

  2. final Scope scope = this.scopes.get(scopeName);

  3. if (scope == null) {

  4. throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");

  5. }

  6. try {

  7. Object scopedInstance = scope.get(beanName, () -> {

  8. beforePrototypeCreation(beanName);

  9. try {

  10. return createBean(beanName, mbd, args);

  11. }

  12. finally {

  13. afterPrototypeCreation(beanName);

  14. }

  15. });

  16. bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);

  17. }

  18. catch (IllegalStateException ex) {

  19. throw new BeanCreationException(beanName,

  20. "Scope '" + scopeName + "' is not active for the current thread; consider " +

  21. "defining a scoped proxy for this bean if you intend to refer to it from a singleton",

  22. ex);

  23. }

核心流程和原型模式一樣,隻不過擷取 bean 執行個體是由

scope.get()

實作,如下:

  1. public Object get(String name, ObjectFactory<?> objectFactory) {

  2. // 擷取 scope 緩存

  3. Map<String, Object> scope = this.threadScope.get();

  4. Object scopedObject = scope.get(name);

  5. if (scopedObject == null) {

  6. scopedObject = objectFactory.getObject();

  7. // 加入緩存

  8. scope.put(name, scopedObject);

  9. }

  10. return scopedObject;

  11. }

對于上面三個子產品,其中最重要的有兩個方法,一個是

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()

就是完成這個工作的。

至此,Spring 加載 bean 的三個部分(LZ自己劃分的)已經分析完畢了。

原文釋出時間為:2018-10-24

本文作者:Java技術驿站

本文來自雲栖社群合作夥伴“

Java技術驿站

”,了解相關資訊可以關注“

”。