Spring IOC(三)單例 bean 的註冊管理

Spring IOC(三)單例 bean 的註冊管理

Spring 系列目錄(http://www.javashuo.com/article/p-kqecupyl-bm.html)html

SingletonBeanRegistry 類圖

在 Spring 中 Bean 有單例和多例之分,只有單例的 bean 纔會被 BeanFactory 管理,這個工做就是由 SingletonBeanRegistry 完成的。java

public interface SingletonBeanRegistry {
    void registerSingleton(String beanName, Object singletonObject);
    Object getSingleton(String beanName);

    boolean containsSingleton(String beanName);
    String[] getSingletonNames();
    int getSingletonCount();
    
    // 暴露全部的 singletonObjects 給外部
    Object getSingletonMutex();
}

SingletonBeanRegistry 的默認實現爲 DefaultSingletonBeanRegistry,主要功能以下:spring

  1. 單例 bean 的註冊和查找。
  2. 擴展了 SingletonBeanRegistry 的功能,還管理 bean 的生命週期,包括 bean 的建立和銷燬。與 bean 生命週期有關的方法以下:
// 1. 建立 bean。先從緩存中獲取 bean,allowEarlyReference=true 時會嘗試從 singletonFactories 獲取提暴露的 bean 
//    緩存中沒有就調用 singletonFactory 方法建立 bean
public Object getSingleton(String beanName);
protected Object getSingleton(String beanName, boolean allowEarlyReference)
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory);

// 2. 銷燬 bean。能夠銷燬所有的 bean,也能夠銷燬指定的 bean,最終都是調用 destroyBean 完成的。
//    銷燬 bean 以前須要先銷燬依賴它的全部 bean,再調用 bean.destroy() 銷燬本身
public void destroySingletons();
public void destroySingleton(String beanName);
protected void destroyBean(String beanName, @Nullable DisposableBean bean);

1、相關屬性

(1) 與單例對象保存有關的四個集合緩存

咱們先從下面四個和單例有關的集合提及。前三個集合和 bean 的建立有關,其中 singletonObjects 保存已經建立成功的 bean 的集合,singletonFactories 和 earlySingletonObjects 屬於中間過程,一旦 bean 建立成功就會刪除對應的記錄,用於解決 bean 屬性注入過程當中的循環依賴的問題。最後一個用來保存當前全部已註冊的 bean。app

// 1.1 保存最終建立成功的單例 beanName -> beanInstance
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 1.2 中間變量,beanName -> Objectfactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 1.3 中間變量,bean 還在建立的時候就能夠獲取,用於檢測循環引用 
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

// 2. 用來保存當前全部已註冊的 bean
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);

這裏涉及用於存儲 bean 的不一樣的 Map,可能讓人感到崩潰,簡單解釋以下:ide

  1. singletonObjects:用於保存 beanName 和建立 bean 實例之間的關係,beanName -> beanInstance
  2. singletonfactories:用於保存 beanname 和建立 bean 的工廠之間的關係,beanName -> Objectfactory
  3. earlySingletonObjects:也是保存 beanName 和建立 bean 實例之間的關係,與 singletonObjects 的不一樣之處在於,當一個單例 bean 被放到這裏面後,那麼當 bean 還在建立過程當中,就能夠經過 getBean 方法獲取到了,其目的是用來檢測循環引用。
  4. registeredsingletons:用來保存當前全部已註冊的 bean

(2) 與單例對象建立有關的幾個屬性函數

// 1. 保存正在建立的 bean
private final Set<String> singletonsCurrentlyInCreation =
            Collections.newSetFromMap(new ConcurrentHashMap<>(16));
// 2. 保存已經建立的 bean 集合,這樣進行依賴循環檢查時
//    若是已經 bean 在 inCreationCheckExclusions 中了,就不用再檢查 singletonsCurrentlyInCreation 了
private final Set<String> inCreationCheckExclusions =
        Collections.newSetFromMap(new ConcurrentHashMap<>(16));
// 3. 保存 bean 建立過程時的異常
private Set<Exception> suppressedExceptions;

(3) 與單例對象銷燬有關的四個集合ui

// 1. 一旦調用 destroySingletons() 銷燬全部的 bean 時就修改成 true 
private boolean singletonsCurrentlyInDestruction = false;

// 2. 下面四個集合都和 bean 的銷燬有關
// 2.1 保存每一個 bean 的 destroy 方法
private final Map<String, Object> disposableBeans = new LinkedHashMap<>();

// 3.1 保存當前 bean 內的全部 bean 信息。containingBean -> containedBean
//      當註冊 containedBeanMap 的同時也會註冊 dependentBeanMap 和 dependenciesForBeanMap
private final Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap<>(16);

// 3.2 保存依賴這個 bean 的全部 bean 的集合,依賴個人。containedBean -> containingBean
//     因此銷燬這個 bean 的同時也要銷燬依賴它的全部 bean
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
// 3.3 保存當前 bean 依賴的全部 bean 的集合,我依賴的。containingBean -> containedBean
//     因此銷燬這個 bean 只要清空對應的記錄便可,而不會銷燬該 bean 依賴的其它 bean
private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);

2、bean 的註冊與刪除

@Override
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
    synchronized (this.singletonObjects) {
        Object oldObject = this.singletonObjects.get(beanName);
        if (oldObject != null) {
            throw new IllegalStateException();
        }
        addSingleton(beanName, singletonObject);
    }
}

// 這個方法只有在本類中才調用,bean 屬性注入完成,直接添加 singletonObject
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);
    }
}

// AbstractAutowireCapableBeanFactory#doCreateBean 調用。註冊 singletonFactory
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

這個沒什麼可說的,主要是要理清這四個集合的功能。能夠看到有兩種方法註冊單例,這兩種狀況都會將 bean 註冊到 registeredSingletons 中:this

  • 一是 bean 建立完成直接註冊 singletonObjects。此時 bean 已經建立完成,直接註冊到 singletonObjects 集合中,同時清理建立過程當中所記錄的各類輔助狀態。
  • 二是 bean 建立開始前先註冊其引用對象 singletonFactory。此時先註冊到 singletonFactories 中,經過 allowEarlyReference=true 能夠查找到提早暴露的這個對象,一旦建立完成調用 addSingleton(String beanName, Object singletonObject) 將其註冊到 singletonObjects 中,這段代碼的邏輯見 getSingleton(String beanName, ObjectFactory<?> singletonFactory) 方法。

3、bean 的生週期

DefaultSingletonBeanRegistry 不只管理了 bean 的註冊,還管理 bean 的生命週期,包括 bean 的建立和銷燬。code

3.1 bean 的建立

Spring 中單例只會被建立一次,後續再獲取 bean 直接從單例緩存中獲取。

  1. 嘗試從緩存中查找。getSingleton(beanName) 直接從緩存中獲取 bean,也能夠指定是否查找提早暴露的 bean(也就是正在建立的 bean)。allowEarlyReference=true 時會查找正在建立的 bean。
  2. 直接建立 bean。當從緩存中獲取不到時,直接會調用 getSingleton(beanName, singletonFactory) 方法建立單例 bean。由於建立的時候會存在依賴注入的狀況,爲了解決可能存在的循環依賴問題,Spring 建立 bean 的原則是不等 bean 建立完成就會將建立 bean 的 ObjectFactory 提前曝光加入到緩存中,這樣經過 getSingleton(beanName, true) 就能夠在緩存中直接查找到這個 bean 的 ObjectFactory。

3.1.1 從緩存中獲取

public Object getSingleton(String beanName) {
    // 參數 true 設置標識容許時期依賴
    return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 在已經建立完成的 singletonObjects 集合中查找
    Object singletonObject = this.singletonObjects.get(beanName);
    // 2. 若是這個 bean 正在建立,則繼續嘗試從 earlySingletonObjects 中查找
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 3. 當某些方法須要提早初始化的時候則會調用 addSingletonFactory 方法
                // 將對應的 ObjectFactory 初始化策略存儲在 singletonFactories
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 調用預先設定的 getObject 方法
                    singletonObject = singletonFactory.getObject();
                    // 記錄在緩存中,earlySingletonObjects 與 singletonFactories 互斥
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

這個方法步驟以下:

  1. 首先嚐試從 singletonObjects 裏面獲取實例
  2. 若是獲取不到再從 earlySingletonObjects 裏面獲取
  3. 若是還獲取不到,再嘗試從 singletonFactories 裏面獲取 beanName 對應的 ObjectFactory,而後調用這個 ObjectFactory 的 getObject 來建立 bean,並放到 earlySingletonObjects 裏面去,而且從 earlySingletonObjects 裏面 remove 掉這個 ObjectFactory

而對於後續的全部內存操做都只爲了循環依賴檢測時候使用,也就是在 allowEarlyReference=true 的狀況下才會使用。

3.1.2 直接建立 bean

上面講解了從緩存中獲取單例的過程,那麼,若是緩存中不存在已經加載的單例 bean 就須要從頭開始 bean 的加載過程了,而 Spring 中使用 getSingleton 的重載方法 getSingleton(beanName, singletonFactory) 實現 bean 的加載過程。

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "'beanName' must not be null");
    synchronized (this.singletonObjects) {
        // 1. 首先檢查對應的 bean 是否已經加載過
        //    由於 singleton 模式其實就是複用已建立的 bean,因此這一步是必須的
        Object singletonObject = this.singletonObjects.get(beanName);
        // 2. 進行 singleton 的 bean 的初始化
        if (singletonObject == null) {
            // 2.1 容器正在銷燬拋出異常
            if (this.singletonsCurrentlyInDestruction) {
                throw new BeanCreationNotAllowedException(beanName);
            }
            // 2.2 將這個 bean 添加到 singletonsCurrentlyInCreation 集合中,這樣就能夠判斷 bean 是否存在建立
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<Exception>();
            }
            try {
                // 2.3 初始化 bean,委託給 ObjectFactory 完成 
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            } catch (IllegalStateException ex) {
                // 2.4 ?????????
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    throw ex;
                }
            } catch (BeanCreationException ex) {
                if (recordSuppressedExceptions) {
                    for (Exception suppressedException : this.suppressedExceptions) {
                        ex.addRelatedCause(suppressedException);
                    }
                }
                throw ex;
            } finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                // 2.5 從 singletonsCurrentlyInCreation 移除該 bean
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // 2.6 建立成功,加入緩存
                addSingleton(beanName, singletonObject);
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}

上述代碼中實際上是使用了回調方法,使得程序能夠在單例建立的先後作一些準備及處理操做,而真正的獲取單例 bean 的方法其實並非在此方法中實現的,其實現邏輯是在 ObjectFactory 類型的實例 singletonFactory 中實現的。而這些準備及處理操做包括以下內容:

(1) 檢查緩存是否已經加載過

(2) 若沒有加載,則記錄 beanName 的正在加載狀態。

(3) 加載單例前記錄加載狀態。可能你會以爲 beforeSingletonCreation 方法是個空實現,裏面沒有任何邏輯,但其實不是,這個函數中作了一個很重要的操做:記錄加載狀態,也就是經過 this.singletonsCurrentlyInCreation.add(beanName) 將當前正要建立的 bean 記錄在緩存中,這樣即可以對循環依賴進行檢測。

protected void beforeSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && 
        !this.singletonsCurrentlyInCreation.add(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

(4) 經過調用參數傳入的 ObjectFactory 的個體 Object 方法實例化 bean

(5) 加載單例後的處理方法調用。同步驟 (3) 的記錄加載狀態類似,當 bean加載結東後須要移除緩存中對該 bean 的正在加載狀態的記錄。

protected void afterSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && 
        !this.singletonsCurrentlyInCreation.remove(beanName)) {
        throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
    }
}

(6) 將結果記錄至緩存並刪除加載 bean 過程當中所記錄的各類輔助狀態。

3.1.3 ObjectFactory 參數

在建立 bean 的過程當中,不少工做都交給 ObjectFactory 這個工廠類完成,那它究竟是什麼呢?

public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

在方法 getSingleton(beanName, singletonFactory)addSingletonFactory(beanName, singletonFactory) 兩種都用到了 ObjectFactory,這兩處的 ObjectFactory 功能並非同樣的。 前者的 ObjectFactory 是用於建立 bean,屬於建立型 ObjectFactory,然後者只是簡單的持有 bean,屬於引用型 ObjectFactory,保存在 singletonFactories 集合中用於解決循環依賴的問題。

(1) 建立型 ObjectFactory

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
            @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
            try {
                return createBean(beanName, mbd, args);
            } catch (BeansException ex) {
                destroySingleton(beanName);
                throw ex;
            }
        }
    });
}

(2) 引用型 ObjectFactory

在 createBean 建立 bean 的過程當中,在實例化 bean 後就會將這個 bean 提早暴露出來,這樣就能夠提早在緩存中拿到正在建立的 bean。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) {
    // 1. 反射建立 bean
    BeanWrapper instanceWrapper = null;
    if (instanceWrapper == null) {
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    final Object bean = instanceWrapper.getWrappedInstance();

    // 2. 將這個 bean 經過 addSingletonFactory 暴露到緩存中,這樣指定 allowEarlyReference=true 就能夠提早查找到
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // getEarlyBeanReference 默認直接將這個 bean 返回了
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // 3. 以後再進行屬性注入和初始化等操做
    Object exposedObject = bean;
    populateBean(beanName, mbd, instanceWrapper);
    exposedObject = initializeBean(beanName, exposedObject, mbd);
}

3.1.4 bean 的狀態管理

bean 進行循環依賴檢查與兩個集合有關:

  • inCreationCheckExclusions 若是 bean 建立完成則添加到該集合中,這樣若是包含要檢查的 bean 就不用再檢查 singletonsCurrentlyInCreation 了。
  • singletonsCurrentlyInCreation bean 建立開始時先添加到這個集合中,建立成功後刪除
// 若是 inCreation=false 則說明對象已經建立成功,直接添加到 inCreationCheckExclusions 集合中,不然就刪除
// 這樣在進行循環依賴檢查時,當這個集合包含該 bean 時就不用檢查 singletonsCurrentlyInCreation
public void setCurrentlyInCreation(String beanName, boolean inCreation) {
    Assert.notNull(beanName, "Bean name must not be null");
    if (!inCreation) {
        this.inCreationCheckExclusions.add(beanName);
    } else {
        this.inCreationCheckExclusions.remove(beanName);
    }
}

// isCurrentlyInCreation 和 isSingletonCurrentlyInCreation 區別仍是沒看明白???
public boolean isCurrentlyInCreation(String beanName) {
    Assert.notNull(beanName, "Bean name must not be null");
    return (!this.inCreationCheckExclusions.contains(beanName) && isActuallyInCreation(beanName));
}
// isActuallyInCreation 方法只有在 isCurrentlyInCreation 中調用,不明白爲何還要單獨抽出來
protected boolean isActuallyInCreation(String beanName) {
    return isSingletonCurrentlyInCreation(beanName);
}
public boolean isSingletonCurrentlyInCreation(String beanName) {
    return this.singletonsCurrentlyInCreation.contains(beanName);
}

3.2 bean 的依賴管理

bean 建立成功後須要註冊 bean 以前的依賴關係,這樣銷燬時才能將依賴它的 bean 也同時銷燬

3.2.1 依賴管理

(1) 註冊依賴

// 保存當前 bean 內的全部 bean。containingBeanName -> containedBeanName
public void registerContainedBean(String containedBeanName, String containingBeanName) {
    synchronized (this.containedBeanMap) {
        Set<String> containedBeans =
                this.containedBeanMap.computeIfAbsent(containingBeanName, k -> new LinkedHashSet<>(8));
        if (!containedBeans.add(containedBeanName)) {
            return;
        }
    }
    registerDependentBean(containedBeanName, containingBeanName);
}

// dependentBeans 保存依賴個人全部 bean。containedBeanName -> containingBeanName
// dependenciesForBeanMap 保存我依賴的全部 bean。containingBeanName -> containedBeanName
public void registerDependentBean(String beanName, String dependentBeanName) {
    // 查找 bean 的註冊名稱
    String canonicalName = canonicalName(beanName);

    synchronized (this.dependentBeanMap) {
        // jdk8 若是 key 不存在設置 value,不然返回 oldValue 
        Set<String> dependentBeans =
                this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
        // 若是已經註冊直接返回
        if (!dependentBeans.add(dependentBeanName)) {
            return;
        }
    }
    synchronized (this.dependenciesForBeanMap) {
        Set<String> dependenciesForBean =
                this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
        dependenciesForBean.add(canonicalName);
    }
}

固然若是這個 bean 有銷燬方法,也會註冊其 DisposableBean

public void registerDisposableBean(String beanName, DisposableBean bean) {
    synchronized (this.disposableBeans) {
        this.disposableBeans.put(beanName, bean);
    }
}

(2) 依賴查找

  • isDependent 查找兩個 bean 的依賴關係
  • getDependentBeans 查找依賴這個 bean 的全部 bean
  • getDependenciesForBean 查找這個 bean 依賴的全部 bean

isDependent 查找兩個 bean 的依賴關係。若是 dependentBeanName 依賴 beanName 則返回 true,這其中會存在間接依賴的狀況,如 beanA -> beanB -> beanC,查詢 beanA -> beanC 的關係。

protected boolean isDependent(String beanName, String dependentBeanName) {
    synchronized (this.dependentBeanMap) {
        return isDependent(beanName, dependentBeanName, null);
    }
}

private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set<String> alreadySeen) {
    // 1. alreadySeen 是已經查找過的 bean,這裏面的 bean 都不存在依賴關係,若是存在則已經返回 true 了
    if (alreadySeen != null && alreadySeen.contains(beanName)) {
        return false;
    }
    // 2. 獲取 bean 的註冊名稱
    String canonicalName = canonicalName(beanName);
    // 3. 查找依賴 beanName 的全部 bean,若是有則確定有依賴,不然還要查找間接依賴
    Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
    if (dependentBeans == null) {
        return false;
    }
    if (dependentBeans.contains(dependentBeanName)) {
        return true;
    }
    // 4. 逐個查找間接依賴
    for (String transitiveDependency : dependentBeans) {
        if (alreadySeen == null) {
            alreadySeen = new HashSet<>();
        }
        alreadySeen.add(beanName);
        if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {
            return true;
        }
    }
    return false;
}

3.3 bean 的銷燬

// 銷燬全部 bean
public void destroySingletons() {
    synchronized (this.singletonObjects) {
        this.singletonsCurrentlyInDestruction = true;
    }

    String[] disposableBeanNames;
    synchronized (this.disposableBeans) {
        disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
    }
    for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
        destroySingleton(disposableBeanNames[i]);
    }

    this.containedBeanMap.clear();
    this.dependentBeanMap.clear();
    this.dependenciesForBeanMap.clear();

    clearSingletonCache();
}

// 銷燬指定的 bean
public void destroySingleton(String beanName) {
    removeSingleton(beanName);
    DisposableBean disposableBean;
    synchronized (this.disposableBeans) {
        disposableBean = (DisposableBean) this.disposableBeans.remove(beanName);
    }
    destroyBean(beanName, disposableBean);
}

真正的銷燬在 destroyBean 中完成。

protected void destroyBean(String beanName, @Nullable DisposableBean bean) {
    // 1. 首先將依賴這個 beanName 的所有銷燬
    Set<String> dependencies;
    // ConcurrentHashMap 爲何還須要同步???
    synchronized (this.dependentBeanMap) {
        dependencies = this.dependentBeanMap.remove(beanName);
    }
    if (dependencies != null) {
        for (String dependentBeanName : dependencies) {
            destroySingleton(dependentBeanName);
        }
    }

    // 2. 真正銷燬該 beanName
    if (bean != null) {
        bean.destroy();
    }

    // 3. 銷燬 containedBeans
    Set<String> containedBeans;
    synchronized (this.containedBeanMap) {
        containedBeans = this.containedBeanMap.remove(beanName);
    }
    if (containedBeans != null) {
        for (String containedBeanName : containedBeans) {
            destroySingleton(containedBeanName);
        }
    }

    // 4. 清理 dependentBeanMap 中殘存的 beanName 記錄
    synchronized (this.dependentBeanMap) {
        for (Iterator<Map.Entry<String, Set<String>>> it = this.dependentBeanMap.entrySet().iterator(); it.hasNext();) {
            Map.Entry<String, Set<String>> entry = it.next();
            Set<String> dependenciesToClean = entry.getValue();
            dependenciesToClean.remove(beanName);
            if (dependenciesToClean.isEmpty()) {
                it.remove();
            }
        }
    }

    // 5. 清理 dependenciesForBeanMap
    this.dependenciesForBeanMap.remove(beanName);
}

天天用心記錄一點點。內容也許不重要,但習慣很重要!

相關文章
相關標籤/搜索