歡迎光臨
每天分享高質量文章

【死磕 Spring】—– IOC 之註冊解析的 BeanDefinition

點選上方“Java技術驛站”,選擇“置頂公眾號”。

有內涵、有價值的文章第一時間送達!

精品專欄

 

DefaultBeanDefinitionDocumentReader.processBeanDefinition() 完成 Bean 標簽解析的核心工作,如下:

  1.    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {

  2.        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);

  3.        if (bdHolder != null) {

  4.            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

  5.            try {

  6.                // Register the final decorated instance.

  7.                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());

  8.            }

  9.            catch (BeanDefinitionStoreException ex) {

  10.                getReaderContext().error("Failed to register bean definition with name '" +

  11.                        bdHolder.getBeanName() + "'", ele, ex);

  12.            }

  13.            // Send registration event.

  14.            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));

  15.        }

  16.    }

解析工作分為三步:1、解析預設標簽;2、解析預設標簽後下得自定義標簽;3、註冊解析後的 BeanDefinition。經過前面兩個步驟的解析,這時的 BeanDefinition 已經可以滿足後續的使用要求了,那麼接下來的工作就是將這些 BeanDefinition 進行註冊,也就是完成第三步。

註冊 BeanDefinition 由 BeanDefinitionReaderUtils.registerBeanDefinition() 完成。如下:

  1.    public static void registerBeanDefinition(

  2.            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)

  3.            throws BeanDefinitionStoreException {

  4.        // 註冊 beanName

  5.        String beanName = definitionHolder.getBeanName();

  6.        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

  7.        // 註冊 alias

  8.        String[] aliases = definitionHolder.getAliases();

  9.        if (aliases != null) {

  10.            for (String alias : aliases) {

  11.                registry.registerAlias(beanName, alias);

  12.            }

  13.        }

  14.    }

首先透過 beanName 註冊 BeanDefinition ,然後再註冊別名 alias。BeanDefinition 的註冊由介面 BeanDefinitionRegistry 定義。

透過 beanName 註冊

BeanDefinitionRegistry.registerBeanDefinition() 實現透過 beanName 註冊 BeanDefinition,如下:

  1.   public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

  2.            throws BeanDefinitionStoreException {

  3.        // 校驗 beanName 與 beanDefinition

  4.        Assert.hasText(beanName, "Bean name must not be empty");

  5.        Assert.notNull(beanDefinition, "BeanDefinition must not be null");

  6.        if (beanDefinition instanceof AbstractBeanDefinition) {

  7.            try {

  8.                // 校驗 BeanDefinition

  9.                // 這是註冊前的最後一次校驗了,主要是對屬性 methodOverrides 進行校驗

  10.                ((AbstractBeanDefinition) beanDefinition).validate();

  11.            }

  12.            catch (BeanDefinitionValidationException ex) {

  13.                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,

  14.                        "Validation of bean definition failed", ex);

  15.            }

  16.        }

  17.        BeanDefinition oldBeanDefinition;

  18.        // 從快取中獲取指定 beanName 的 BeanDefinition

  19.        oldBeanDefinition = this.beanDefinitionMap.get(beanName);

  20.        /**

  21.         * 如果存在

  22.         */

  23.        if (oldBeanDefinition != null) {

  24.            // 如果存在但是不允許改寫,丟擲異常

  25.            if (!isAllowBeanDefinitionOverriding()) {

  26.                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,

  27.                        "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +

  28.                                "': There is already [" + oldBeanDefinition + "] bound.");

  29.            }

  30.            //

  31.            else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {

  32.                // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE

  33.                if (this.logger.isWarnEnabled()) {

  34.                    this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +

  35.                            "' with a framework-generated bean definition: replacing [" +

  36.                            oldBeanDefinition + "] with [" + beanDefinition + "]");

  37.                }

  38.            }

  39.            // 改寫 beanDefinition 與 被改寫的 beanDefinition 不是同類

  40.            else if (!beanDefinition.equals(oldBeanDefinition)) {

  41.                if (this.logger.isInfoEnabled()) {

  42.                    this.logger.info("Overriding bean definition for bean '" + beanName +

  43.                            "' with a different definition: replacing [" + oldBeanDefinition +

  44.                            "] with [" + beanDefinition + "]");

  45.                }

  46.            }

  47.            else {

  48.                if (this.logger.isDebugEnabled()) {

  49.                    this.logger.debug("Overriding bean definition for bean '" + beanName +

  50.                            "' with an equivalent definition: replacing [" + oldBeanDefinition +

  51.                            "] with [" + beanDefinition + "]");

  52.                }

  53.            }

  54.            // 允許改寫,直接改寫原有的 BeanDefinition

  55.            this.beanDefinitionMap.put(beanName, beanDefinition);

  56.        }

  57.        /**

  58.         * 不存在

  59.         */

  60.        else {

  61.             // 檢測建立 Bean 階段是否已經開啟,如果開啟了則需要對 beanDefinitionMap 進行併發控制

  62.            if (hasBeanCreationStarted()) {

  63.                // beanDefinitionMap 為全域性變數,避免併發情況

  64.                synchronized (this.beanDefinitionMap) {

  65.                    //

  66.                    this.beanDefinitionMap.put(beanName, beanDefinition);

  67.                    List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);

  68.                    updatedDefinitions.addAll(this.beanDefinitionNames);

  69.                    updatedDefinitions.add(beanName);

  70.                    this.beanDefinitionNames = updatedDefinitions;

  71.                    if (this.manualSingletonNames.contains(beanName)) {

  72.                        Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);

  73.                        updatedSingletons.remove(beanName);

  74.                        this.manualSingletonNames = updatedSingletons;

  75.                    }

  76.                }

  77.            }

  78.            else {

  79.                // 不會存在併發情況,直接設定

  80.                this.beanDefinitionMap.put(beanName, beanDefinition);

  81.                this.beanDefinitionNames.add(beanName);

  82.                this.manualSingletonNames.remove(beanName);

  83.            }

  84.            this.frozenBeanDefinitionNames = null;

  85.        }

  86.        if (oldBeanDefinition != null || containsSingleton(beanName)) {

  87.            // 重新設定 beanName 對應的快取

  88.            resetBeanDefinition(beanName);

  89.        }

  90.    }

處理過程如下:

  • 首先 BeanDefinition 進行校驗,該校驗也是註冊過程中的最後一次校驗了,主要是對 AbstractBeanDefinition 的 methodOverrides 屬性進行校驗

  • 根據 beanName 從快取中獲取 BeanDefinition,如果快取中存在,則根據 allowBeanDefinitionOverriding 標誌來判斷是否允許改寫,如果允許則直接改寫,否則丟擲 BeanDefinitionStoreException 異常

  • 若快取中沒有指定 beanName 的 BeanDefinition,則判斷當前階段是否已經開始了 Bean 的建立階段(),如果是,則需要對 beanDefinitionMap 進行加鎖控制併發問題,否則直接設定即可。對於 hasBeanCreationStarted() 方法後續做詳細介紹,這裡不過多闡述。

  • 若快取中存在該 beanName 或者 單利 bean 集合中存在該 beanName,則呼叫 resetBeanDefinition() 重置 BeanDefinition 快取。

其實整段程式碼的核心就在於 this.beanDefinitionMap.put(beanName,beanDefinition); 。BeanDefinition 的快取也不是神奇的東西,就是定義 map ,key 為 beanName,value 為 BeanDefinition。

註冊 alias

BeanDefinitionRegistry.registerAlias 完成 alias 的註冊。

  1.    public void registerAlias(String name, String alias) {

  2.        // 校驗 name 、 alias

  3.        Assert.hasText(name, "'name' must not be empty");

  4.        Assert.hasText(alias, "'alias' must not be empty");

  5.        synchronized (this.aliasMap) {

  6.            // name == alias 則去掉alias

  7.            if (alias.equals(name)) {

  8.                this.aliasMap.remove(alias);

  9.            }

  10.            else {

  11.                // 快取快取記錄

  12.                String registeredName = this.aliasMap.get(alias);

  13.                if (registeredName != null) {

  14.                    // 快取中的相等,則直接傳回

  15.                    if (registeredName.equals(name)) {

  16.                        // An existing alias - no need to re-register

  17.                        return;

  18.                    }

  19.                    // 不允許則丟擲異常

  20.                    if (!allowAliasOverriding()) {

  21.                        throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +

  22.                                name + "': It is already registered for name '" + registeredName + "'.");

  23.                    }

  24.                }

  25.                // 當 A --> B 存在時,如果再次出現 A --> B --> C 則丟擲異常

  26.                checkForAliasCircle(name, alias);

  27.                // 註冊 alias

  28.                this.aliasMap.put(alias, name);

  29.            }

  30.        }

  31.    }

註冊 alias 和註冊 BeanDefinition 的過程差不多。在最好呼叫了 checkForAliasCircle() 來對別名進行了檢測。

  1.    public boolean hasAlias(String name, String alias) {

  2.        for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {

  3.            String registeredName = entry.getValue();

  4.            if (registeredName.equals(name)) {

  5.                String registeredAlias = entry.getKey();

  6.                return (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias));

  7.            }

  8.        }

  9.        return false;

  10.    }

如果 name 、 alias 為 1 、3 ,則構成 (1,3),加入集合中存在(A,1)、(3,A)的情況則會出錯。

到這裡 BeanDefinition、alias 都已經註入到快取中,下一步則是等待初始化使用了。

【死磕 Spring】----- IOC 之解析 bean 標簽:解析自定義標簽

【死磕 Spring】----- IOC 之解析 bean 標簽:constructor-arg、property 子元素

【死磕 Spring】—– IOC 之解析 bean 標簽:meta、lookup-method、replace-method)

【死磕 Spring】----- IOC 之解析 bean 標簽:開啟解析行程

【死磕 Spring】----- IOC 之解析 bean 標簽:BeanDefinition

【死磕 Spring】----- IOC 之 獲取 Document 物件

【死磕 Spring】----- IOC 之 註冊 BeanDefinition

【死磕 Spring】----- IOC 之 獲取驗證模型

【死磕 Spring】----- IOC 之 Spring 統一資源載入策略

【死磕 Spring】----- IOC 之深入理解 Spring IoC

END

我是 Java 技術驛站,感謝有你

贊(0)

分享創造快樂

© 2024 知識星球   網站地圖