1. 程式人生 > >四、原始碼分析 Spring 之IOC 容器的高階特性

四、原始碼分析 Spring 之IOC 容器的高階特性

高階特性介紹

通過前面對 Spring IOC 容器的原始碼分析,我們已經基本上了解了 Spring IOC 容器對 Bean 定義資源的定位、讀入和解析過程,同時也清楚了當使用者通過 getBean 方法向 IOC 容器獲取被管理的 Bean 時,IOC 容器對 Bean 進行的初始化和依賴注入過程,這些是 Spring IOC 容器的基本功能特性。

Spring IOC 容器還有一些高階特性,如使用 lazy-init 屬性對 Bean 預初始化、FactoryBean 產生或者修飾 Bean 物件的生成、IOC 容器初始化 Bean 過程中使用 BeanPostProcessor 後置處理器對 Bean 宣告週期事件管理和 IOC 容器的 autowiring 自動裝配功能等。

Spring IOC 容器的 lazy-init 屬性實現預例項化

通過前面我們對 IOC 容器的實現和工作原理分析,我們知道 IOC 容器的初始化過程就是對 Bean 定義資源的定位、載入和註冊,此時容器對 Bean 的依賴注入並沒有發生,依賴注入主要是在應用程式第一次向容器索取 Bean 時,通過 getBean 方法的呼叫完成。

當 Bean 定義資源的元素中配置了 lazy-init 屬性時,容器將會在初始化的時候對所配置的 Bean 進行預例項化,Bean 的依賴注入在容器初始化的時候就已經完成。這樣,當應用程式第一次向容器索取被管理的 Bean 時,就不用再初始化和對 Bean 進行依賴注入了,直接從容器中獲取已經完成依賴注入的現成 Bean,可以提高應用第一次向容器獲取 Bean 的效能

。下面我們通過程式碼分析容器預例項化的實現過程:

1、refresh()

先從 IOC 容器的初始化過程開始,通過前面文章分析,我們知道 IOC 容器讀入已經定位的 Bean 定義資源是從 refresh 方法開始的,我們首先從 AbstractApplicationContext 類的 refresh 方法入手分析。原始碼如下:

//容器初始化的過程,讀入 Bean 定義資源,並解析註冊
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor)
{ //呼叫容器準備重新整理的方法,獲取容器的當時時間,同時給容器設定同步標識 prepareRefresh(); //告訴子類啟動 refreshBeanFactory()方法,Bean 定義資原始檔的載入從子類的 refreshBeanFactory()方法啟動 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); //為 BeanFactory 配置容器特性,例如類載入器、事件處理器等 prepareBeanFactory(beanFactory); try { //為容器的某些子類指定特殊的 BeanPost 事件處理器 postProcessBeanFactory(beanFactory); //呼叫所有註冊的 BeanFactoryPostProcessor 的 Bean invokeBeanFactoryPostProcessors(beanFactory); //為 BeanFactory 註冊 BeanPost 事件處理器. //BeanPostProcessor 是 Bean 後置處理器,用於監聽容器觸發的事件 registerBeanPostProcessors(beanFactory); //初始化資訊源,和國際化相關. initMessageSource(); //初始化容器事件傳播器. initApplicationEventMulticaster(); //呼叫子類的某些特殊 Bean 初始化方法 onRefresh(); //為事件傳播器註冊事件監聽器. registerListeners(); //這裡是對容器 lazy-init 屬性進行處理的入口方法 ————> 2 finishBeanFactoryInitialization(beanFactory); //初始化容器的生命週期事件處理器,併發布容器的生命週期事件 finishRefresh(); } catch (BeansException ex) { //銷燬以建立的單態 Bean destroyBeans(); //取消 refresh 操作,重置容器的同步標識. cancelRefresh(ex); throw ex; } } }

在 refresh 方法中 ConfigurableListableBeanFactorybeanFactory = obtainFreshBeanFactory();啟動了 Bean 定義資源的載入、註冊過程,而 finishBeanFactoryInitialization 方法是對註冊後的 Bean定義中的預例項化(lazy-init = false,Spring 預設就是預例項化,即為 true)的 Bean 進行處理的地方。

2、finishBeanFactoryInitialization 處理預例項化 Bean

當Bean定義資源被載入IOC容器之後,容器將Bean定義資源解析為容器內部的資料結構BeanDefinition註冊到容器中,AbstractApplicationContext 類中的 finishBeanFactoryInitialization 方法對配置了預例項化屬性的 Bean 進行預初始化過程,原始碼如下:

//對配置了 lazy-init 屬性的 Bean 進行預例項化處理
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    //這是 Spring3 以後新加的程式碼,為容器指定一個轉換服務(ConversionService)
    //在對某些 Bean 屬性進行轉換時使用
    if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) && beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
        beanFactory.setConversionService(beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
    }
    //為了型別匹配,停止使用臨時的類載入器
    beanFactory.setTempClassLoader(null);
    //快取容器中所有註冊的 BeanDefinition 元資料,以防被修改
    beanFactory.freezeConfiguration();
    //對配置了 lazy-init 屬性的單態模式 Bean 進行預例項化處理 ————> 3
    beanFactory.preInstantiateSingletons();
}

ConfigurableListableBeanFactory 是一個介面,其 preInstantiateSingletons 方法由其子類DefaultListableBeanFactory 提供。

3、DefaultListableBeanFactory 對配置 lazy-init 屬性單態 Bean 的預例項化

//對配置 lazy-init 屬性單態 Bean 的預例項化
public void preInstantiateSingletons() throws BeansException {
    if (this.logger.isInfoEnabled()) {
        this.logger.info("Pre-instantiating singletons in " + this);
    }
    //在對配置 lazy-init 屬性單態 Bean 的預例項化過程中,必須多執行緒同步,以確保資料一致性
    synchronized (this.beanDefinitionMap) {
        for (String beanName : this.beanDefinitionNames) {
            //獲取指定名稱的 Bean 定義
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            //Bean 不是抽象的,是單態模式的,且 lazy-init 屬性配置為 false
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                //如果指定名稱的 bean 是建立容器的 Bean
                if (isFactoryBean(beanName)) {
                    //FACTORY_BEAN_PREFIX=”&”,當 Bean 名稱前面加”&”符號時,獲取的是產生容器物件本身,而不是容器產生的 Bean.
                    //呼叫 getBean 方法,觸發容器對 Bean 例項化和依賴注入過程
                    final FactoryBean factory = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName);
                    //標識是否需要預例項化
                    boolean isEagerInit;
                    if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                        //一個匿名內部類
                        isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
                            public Boolean run() {
                                return ((SmartFactoryBean) factory).isEagerInit();
                            }
                        }, getAccessControlContext());
                    } else {
                        isEagerInit = factory instanceof SmartFactoryBean && ((SmartFactoryBean)
                        factory).isEagerInit();
                    } 
                    if (isEagerInit) {
                        //呼叫 getBean 方法,觸發容器對 Bean 例項化和依賴注入過程
                        getBean(beanName);
                    }
                } else {
                    //呼叫 getBean 方法,觸發容器對 Bean 例項化和依賴注入過程
                    getBean(beanName);
                }
            }
        }
    }
}

通過對 lazy-init 處理原始碼的分析,我們可以看出,如果設定了 lazy-init 屬性,則容器在完成 Bean 定義的註冊之後,會通過 getBean 方法,觸發對指定 Bean 的初始化和依賴注入過程,這樣當應用第一次向容器索取所需的 Bean 時,容器不再需要對 Bean 進行初始化和依賴注入,直接從已經完成例項化和依賴注入的 Bean 中取一個現成的 Bean,這樣就提高了第一次獲取 Bean 的效能。

FactoryBean 的實現

在 Spring 中,有兩個很容易混淆的類:BeanFactory 和 FactoryBean。

BeanFactory:Bean 工廠,是一個工廠(Factory),我們 Spring IOC 容器的最頂層介面就是這個BeanFactory,它的作用是管理 Bean,即例項化、定位、配置應用程式中的物件及建立這些物件間的依賴。

FactoryBean:工廠 Bean,是一個 Bean,作用是產生其他 bean 例項。通常情況下,這種 bean 沒有什麼特別的要求,僅需要提供一個工廠方法,該方法用來返回其他 bean 例項。通常情況下,bean 無須自己實現工廠模式,Spring 容器擔任工廠角色;但少數情況下,容器中的 bean 本身就是工廠,其作用是產生其它 bean 例項。

當用戶使用容器本身時,可以使用轉義字元”&”來得到 FactoryBean 本身,以區別通過 FactoryBean產生的例項物件和 FactoryBean 物件本身。在 BeanFactory 中通過如下程式碼定義了該轉義字元:StringFACTORY_BEAN_PREFIX = “&”;

如果 myJndiObject 是一個 FactoryBean,則使用&myJndiObject 得到的是 myJndiObject 物件,而不是myJndiObject 產生出來的物件。

1、FactoryBean 的原始碼如下

//工廠 Bean,用於產生其他物件
public interface FactoryBean<T> {
    //獲取容器管理的物件例項
    T getObject() throws Exception;
    //獲取 Bean 工廠建立的物件的型別
    Class<?> getObjectType();
    //Bean 工廠建立的物件是否是單態模式,如果是單態模式,則整個容器中只有一個例項物件,每次請求都返回同一個例項物件
    boolean isSingleton();
}

2、AbstractBeanFactory 的 getBean 方法呼叫 FactoryBean

在前面我們分析 Spring IOC 容器例項化 Bean 並進行依賴注入過程的原始碼時,提到在 getBean 方法觸發容器例項化 Bean 的時候會呼叫 AbstractBeanFactory 的 doGetBean 方法來進行例項化的過程,原始碼如下:

//真正實現向 IOC 容器獲取 Bean 的功能,也是觸發依賴注入功能的地方
@SuppressWarnings("unchecked")
protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)throws BeansException {
    //根據指定的名稱獲取被管理 Bean 的名稱,剝離指定名稱中對容器的相關依賴
    //如果指定的是別名,將別名轉換為規範的 Bean 名稱
    final String beanName = transformedBeanName(name);
    Object bean;
    //先從快取中取是否已經有被建立過的單態型別的 Bean,對於單態模式的 Bean 整個 IoC 容器中只建立一次,不需要重複建立
    Object sharedInstance = getSingleton(beanName);
    //IoC 容器建立單態模式 Bean 例項物件
    if (sharedInstance != null && args == null) {
        if (logger.isDebugEnabled()) {
            //如果指定名稱的 Bean 在容器中已有單態模式的 Bean 被建立,直接返回已經建立的 Bean
            if (isSingletonCurrentlyInCreation(beanName)) {
                logger.debug("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference");
            } else {
                logger.debug("Returning cached instance of singleton bean '" + beanName + "'");
            }
        }
        //獲取給定 Bean 的例項物件,主要是完成 FactoryBean 的相關處理 ————> 2
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
    ……
}
//獲取給定 Bean 的例項物件,主要是完成 FactoryBean 的相關處理
protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {
    //容器已經得到了 Bean 例項物件,這個例項物件可能是一個普通的 Bean,也可能是一個工廠 Bean,
    //如果是一個工廠 Bean,則使用它建立一個 Bean 例項物件,
    //如果呼叫本身就想獲得一個容器的引用,則指定返回這個工廠 Bean 例項物件
    //如果指定的名稱是容器的解引用(dereference,即是物件本身而非記憶體地址),
    //且 Bean 例項也不是建立 Bean 例項物件的工廠 Bean
    if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
        throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
    }
    //如果 Bean 例項不是工廠 Bean,或者指定名稱是容器的解引用,
    //呼叫者向獲取對容器的引用,則直接返回當前的 Bean 例項
    if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
        return beanInstance;
    }
    //處理指定名稱不是容器的解引用,或者根據名稱獲取的 Bean 例項物件是一個工廠 Bean
    //使用工廠 Bean 建立一個 Bean 的例項物件
    Object object = null;
    if (mbd == null) {
        //從 Bean 工廠快取中獲取給定名稱的 Bean 例項物件
        object = getCachedObjectForFactoryBean(beanName);
    }
    //讓 Bean 工廠生產給定名稱的 Bean 物件例項
    if (object == null) {
        FactoryBean factory = (FactoryBean) beanInstance;
        //如果從 Bean 工廠生產的 Bean 是單態模式的,則快取
        if (mbd == null && containsBeanDefinition(beanName)) {
            //從容器中獲取指定名稱的 Bean 定義,如果繼承基類,則合併基類相關屬性
            mbd = getMergedLocalBeanDefinition(beanName);
        }
        //如果從容器得到 Bean 定義資訊,並且 Bean 定義資訊不是虛構的,
        //則讓工廠 Bean 生產 Bean 例項物件
        boolean synthetic = (mbd != null && mbd.isSynthetic());
        //呼叫 FactoryBeanRegistrySupport 類的 getObjectFromFactoryBean 方法,
        //實現工廠 Bean 生產 Bean 物件例項的過程 ————> 3
        object = getObjectFromFactoryBean(factory, beanName, !synthetic);
    }
    return object;
}

在上面獲取給定 Bean 的例項物件的 getObjectForBeanInstance 方法中,會呼叫FactoryBeanRegistrySupport 類的 getObjectFromFactoryBean 方法,該方法實現了 Bean 工廠生產 Bean例項物件。

Dereference(解引用):一個在 C/C++ 中應用比較多的術語,在 C++ 中,”*”是解引用符號,而”&”是引用符號,解引用是指變數指向的是所引用物件的本身資料,而不是引用物件的記憶體地址

3、AbstractBeanFactory 生產 Bean 例項物件

AbstractBeanFactory 類中生產 Bean 例項物件的主要原始碼如下:

//Bean 工廠生產 Bean 例項物件
protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName, boolean shouldPostProcess) {
    //Bean 工廠是單態模式,並且 Bean 工廠快取中存在指定名稱的 Bean 例項物件
    if (factory.isSingleton() && containsSingleton(beanName)) {
        //多執行緒同步,以防止資料不一致
        synchronized (getSingletonMutex()) {
            //直接從 Bean 工廠快取中獲取指定名稱的 Bean 例項物件
            Object object = this.factoryBeanObjectCache.get(beanName);
            //Bean 工廠快取中沒有指定名稱的例項物件,則生產該例項物件
            if (object == null) {
                //呼叫 Bean 工廠的 getObject 方法生產指定 Bean 的例項物件 ————> 3
                object = doGetObjectFromFactoryBean(factory, beanName, shouldPostProcess);
                //將生產的例項物件新增到 Bean 工廠快取中
                this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
            }
            return (object != NULL_OBJECT ? object : null);
        }
    }
    //呼叫 Bean 工廠的 getObject 方法生產指定 Bean 的例項物件 ————> 3
    else {
        return doGetObjectFromFactoryBean(factory, beanName, shouldPostProcess);
    }
}
//呼叫 Bean 工廠的 getObject 方法生產指定 Bean 的例項物件
private Object doGetObjectFromFactoryBean(final FactoryBean factory, final String beanName, final boolean shouldPostProcess)throws BeanCreationException {
    Object object;
    try {
        if (System.getSecurityManager() != null) {
            AccessControlContext acc = getAccessControlContext();
            try {
                //實現 PrivilegedExceptionAction 介面的匿名內建類
                //根據 JVM 檢查許可權,然後決定 BeanFactory 建立例項物件
                object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
                    public Object run() throws Exception {
                        //呼叫 BeanFactory 介面實現類的建立物件方法
                        return factory.getObject();
                    }
                }, acc);
            } catch (PrivilegedActionException pae) {
                throw pae.getException();
            }
        } else {
            //呼叫 BeanFactory 介面實現類的建立物件方法
            object = factory.getObject();
        }
    } catch (FactoryBeanNotInitializedException ex) {
        throw new BeanCurrentlyInCreationException(beanName, ex.toString());
    } catch (Throwable ex) {
        throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
    }
    //創建出來的例項物件為 null,或者因為單態物件正在建立而返回 null
    if (object == null && isSingletonCurrentlyInCreation(beanName)) {
        throw new BeanCurrentlyInCreationException( beanName, "FactoryBean which is currently in creation returned null from getObject");
    }
    //為創建出來的 Bean 例項物件新增 BeanPostProcessor 後置處理器
    if (object != null && shouldPostProcess) {
        try {
            object = postProcessObjectFromFactoryBean(object, beanName);
        } catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Post-processing of the FactoryBean's object failed", ex);
        }
    }
    return object;
}

從上面的原始碼分析中,我們可以看出,BeanFactory 介面呼叫其實現類的 getObject 方法來實現建立 Bean 例項物件的功能。

4、工廠 Bean 的實現類 getObject 方法建立 Bean 例項物件

FactoryBean的實現類有非常多,比如:Proxy、RMI、JNDI、ServletContextFactoryBean等等,FactoryBean 介面為 Spring 容器提供了一個很好的封裝機制,具體的 getObject 有不同的實現類根據不同的實現策略來具體提供,我們分析一個最簡單的 AnnotationTestFactoryBean 的實現原始碼:

public class AnnotationTestBeanFactory implements FactoryBean<IJmxTestBean> {
    private final FactoryCreatedAnnotationTestBean instance = new FactoryCreatedAnnotationTestBean();
    public AnnotationTestBeanFactory() {
        this.instance.setName("FACTORY");
    }
    //AnnotationTestBeanFactory 產生 Bean 例項物件的實現
    public IJmxTestBean getObject() throws Exception {
        return this.instance;
    }
    public Class<? extends IJmxTestBean> getObjectType() {
        return FactoryCreatedAnnotationTestBean.class;
    }
    public boolean isSingleton() {
        return true;
    }
}

其他的 Proxy,RMI,JNDI 等等,都是根據相應的策略提供 getObject 的實現。這裡不做一一分析,這已經不是 Spring 的核心功能,有需要的時候再去深入研究。

BeanPostProcessor 後置處理器的實現

BeanPostProcessor 後置處理器是 Spring IOC 容器經常使用到的一個特性,這個 Bean 後置處理器是一個監聽器,可以監聽容器觸發的 Bean 宣告週期事件。後置處理器向容器註冊以後,容器中管理的 Bean就具備了接收 IOC 容器事件回撥的能力。
BeanPostProcessor 的使用非常簡單,只需要提供一個實現介面 BeanPostProcessor 的實現類,然後在Bean 的配置檔案中設定即可。

1、BeanPostProcessor 的原始碼如下

package org.springframework.beans.factory.config;
import org.springframework.beans.BeansException;
public interface BeanPostProcessor {
    //為在 Bean 的初始化前提供回撥入口
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    //為在 Bean 的初始化之後提供回撥入口
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

這兩個回撥的入口都是和容器管理的 Bean 的生命週期事件緊密相關,可以為使用者提供在 Spring IOC
容器初始化 Bean 過程中自定義的處理操作。

2、AbstractAutowireCapableBeanFactory 類對容器生成的 Bean 新增後置處理器

BeanPostProcessor後置處理器的呼叫發生在Spring IOC容器完成對Bean例項物件的建立和屬性的依賴注入完成之後,在對 Spring 依賴注入的原始碼分析過程中我們知道,當應用程式第一次呼叫 getBean方法(lazy-init 預例項化除外)向 Spring IOC 容器索取指定 Bean 時觸發 Spring IOC 容器建立 Bean例項物件並進行依賴注入的過程,其中真正實現建立 Bean 物件並進行依賴注入的方法是AbstractAutowireCapableBeanFactory 類的 doCreateBean 方法,主要原始碼如下:

//真正建立 Bean 的方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
    //建立 Bean 例項物件
    ……
    try {
    //對 Bean 屬性進行依賴注入
    populateBean(beanName, mbd, instanceWrapper);
        if (exposedObject != null) {
            //在對 Bean 例項物件生成和依賴注入完成以後,開始對 Bean 例項物件
            //進行初始化 ,為 Bean 例項物件應用 BeanPostProcessor 後置處理器
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
    } catch (Throwable ex) {
        if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException)ex).getBeanName())) {
            throw (BeanCreationException) ex;
        }
        ……
        //為應用返回所需要的例項物件
        return exposedObject;
    }
}

從上面的程式碼中我們知道,為 Bean 例項物件新增 BeanPostProcessor 後置處理器的入口的是
initializeBean 方法。

3、initializeBean 方法為容器產生的 Bean 例項物件新增 BeanPostProcessor 後置處理器

同樣在 AbstractAutowireCapableBeanFactory 類中,initializeBean 方法實現為容器建立的 Bean 例項
物件新增 BeanPostProcessor 後置處理器,原始碼如下:
//初始容器建立的 Bean 例項物件,為其新增 BeanPostProcessor 後置處理器

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd){
    //JDK 的安全機制驗證許可權
    if (System.getSecurityManager() != null) {
        //實現 PrivilegedAction 介面的匿名內部類
        AccessController.doPrivileged(new PrivilegedAction<Object>