天天看點

Spring IoC容器的初始化

文章目錄

    • Resource定位
    • 通過傳回的resource對象,進行BeanDefinition的載入
    • 将BeanDefiniton注冊到容器中
    • 小結

關于Spirng IoC容器的初始化過程在《Spirng技術内幕:深入解析Spring架構與設計原理》一書中有明确的指出,IoC容器的初始化過程可以分為三步:

  • Resource定位(Bean的定義檔案定位)
  • 将Resource定位好的資源載入到BeanDefinition
  • 将BeanDefiniton注冊到容器中

Resource定位

Resource是Sping中用于封裝I/O操作的接口。正如前面所見,在建立spring容器時,通常要通路XML配置檔案,除此之外還可以通過通路檔案類型、二進制流等方式通路資源,還有當需要網絡上的資源時可以通過通路URL,Spring把這些檔案統稱為Resource。

常用的resource資源類型如下:

  

FileSystemResource

:以檔案的絕對路徑方式進行通路資源,效果類似于Java中的File;

  

  

ClassPathResource

:以類路徑的方式通路資源,效果類似于this.getClass().getResource("/").getPath();

  

  

ServletContextResource

:web應用根目錄的方式通路資源,效果類似于request.getServletContext().getRealPath("");

  

  

UrlResource

:通路網絡資源的實作類。例如file: http: ftp:等字首的資源對象;

  

  

ByteArrayResource

: 通路位元組數組資源的實作類。

Spring提供了ResourceLoader接口用于實作不同的Resource加載政策,該接口的執行個體對象中可以擷取一個resource對象,也就是說将不同Resource執行個體的建立交給ResourceLoader的實作類來處理。

ResourceLoader接口中隻定義了兩個方法:

Resource getResource(String location); //通過提供的資源location參數擷取Resource執行個體
ClassLoader getClassLoader(); // 擷取ClassLoader,通過ClassLoader可将資源載入JVM
           

注:ApplicationContext的所有實作類都實作了RecourceLoader接口,是以可以直接調用getResource方法擷取Resoure對象。不同的ApplicatonContext實作類使用getResource方法取得的資源類型不同,例如:FileSystemXmlApplicationContext.getResource擷取的就是FileSystemResource執行個體;ClassPathXmlApplicationContext.gerResource擷取的就是ClassPathResource執行個體;XmlWebApplicationContext.getResource擷取的就是ServletContextResource執行個體,另外像不需要通過xml直接使用注解@Configuation方式加載資源的AnnotationConfigApplicationContext等等。

在資源定位過程完成以後,就為資源檔案中的bean的載入創造了I/O操作的條件。

通過傳回的resource對象,進行BeanDefinition的載入

在Spring中配置檔案主要格式是XML,對于用來讀取XML型資源檔案來進行初始化的IoC 容器而言,該類容器會使用到AbstractXmlApplicationContext類,該類定義了一個名為loadBeanDefinitions(DefaultListableBeanFactory beanFactory) 的方法用于擷取BeanDefinition:

// 該方法屬于AbstractXmlApplicationContect類
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    beanDefinitionReader.setEnvironment(this.getEnvironment());
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
    this.initBeanDefinitionReader(beanDefinitionReader);
    // 用于擷取BeanDefinition
    this.loadBeanDefinitions(beanDefinitionReader);
}
           

此方法在具體執行過程中首先會new一個與容器對應的BeanDefinitionReader型執行個體對象,然後将生成的BeanDefintionReader執行個體作為參數傳入loadBeanDefintions(XmlBeanDefinitionReader),繼續往下執行載入BeanDefintion的過程。例如AbstractXmlApplicationContext有兩個實作類:FileSystemXmlApplicationContext、ClassPathXmlApplicationContext,這些容器在調用此方法時會建立一個XmlBeanDefinitionReader類對象專門用來載入所有的BeanDefinition。

下面以XmlBeanDefinitionReader對象載入BeanDefinition為例,使用源碼說明載入BeanDefinition的過程:

// 該方法屬于AbstractXmlApplicationContect類
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        Resource[] configResources = getConfigResources();//擷取所有定位到的resource資源位置(使用者定義)
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);//載入resources
        }
        String[] configLocations = getConfigLocations();//擷取所有本地配置檔案的位置(容器自身)
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);//載入resources
        }
}
           

通過上面代碼将使用者定義的資源以及容器本身需要的資源全部加載到reader中,reader.loadBeanDefinitions方法的源碼如下:

// 該方法屬于AbstractBeanDefinitionReader類, 父接口BeanDefinitionReader
@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
    Assert.notNull(resources, "Resource array must not be null");
    int counter = 0;
    for (Resource resource : resources) {
        // 将所有資源全部加載,交給AbstractBeanDefinitionReader的實作子類處理這些resource
        counter += loadBeanDefinitions(resource);
    }
    return counter;
}
           

BeanDefinitionReader接口定義了 int loadBeanDefinitions(Resource resource)方法:

int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;

int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;

           

XmlBeanDefinitionReader 類實作了BeanDefinitionReader接口中的loadBeanDefinitions(Resource)方法。XmlBeanDefinitionReader類中幾主要對加載的所有resource開始進行處理,大緻過程是,先将resource包裝為EncodeResource類型,然後處理,為生成BeanDefinition對象做準備,其主要幾個方法的源碼如下:

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        // 包裝resource為EncodeResource類型
        return loadBeanDefinitions(new EncodedResource(resource)); 
    }

    // 加載包裝後的EncodeResource資源
    public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }

        try {
            // 通過resource對象得到XML檔案内容輸入流,并為IO的InputSource做準備
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                // Create a new input source with a byte stream.
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                // 開始準備 load bean definitions from the specified XML file
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
    }

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            // 擷取指定資源的驗證模式
            int validationMode = getValidationModeForResource(resource);

            // 從資源對象中加載DocumentL對象,大緻過程為:将resource資源檔案的内容讀入到document中
            // DocumentLoader在容器讀取XML檔案過程中有着舉足輕重的作用!
            // XmlBeanDefinitionReader執行個體化時會建立一個DefaultDocumentLoader型的私有屬性,繼而調用loadDocument方法
            // inputSource--要加載的文檔的輸入源
            Document doc = this.documentLoader.loadDocument(
                    inputSource, this.entityResolver, this.errorHandler, validationMode, this.namespaceAware);
            
            // 将document檔案的bean封裝成BeanDefinition,并注冊到容器
            return registerBeanDefinitions(doc, resource);
        }
        catch ...(略)
    }
           

上面代碼分析到了registerBeanDefinitions(doc, resource)這一步,也就是準備将Document中的Bean按照Spring bean語義進行解析并轉化為BeanDefinition類型,這個方法的具體過程如下:

/**
 * 屬于XmlBeanDefinitionReader類
 * Register the bean definitions contained in the given DOM document.
 * @param doc the DOM document
 * @param resource
 * @return the number of bean definitions found
 * @throws BeanDefinitionStoreException
 */
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    // 擷取到DefaultBeanDefinitionDocumentReader執行個體
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    // 擷取容器中bean的數量
    int countBefore = getRegistry().getBeanDefinitionCount();
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}
           

通過 XmlBeanDefinitionReader 類中的私有屬性 documentReaderClass 可以獲得一個 DefaultBeanDefinitionDocumentReader 執行個體對象:

private Class<?> documentReaderClass = DefaultBeanDefinitionDocumentReader.class;

protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
   return BeanDefinitionDocumentReader.class.cast(BeanUtils.instantiateClass(this.documentReaderClass));
}
           

DefaultBeanDefinitionDocumentReader實作了BeanDefinitionDocumentReader接口,它的registerBeanDefinitions方法定義如下:

// DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;

    logger.debug("Loading bean definitions");
    // 擷取doc的root節點,通過該節點能夠通路所有的子節點
    Element root = doc.getDocumentElement();
    // 處理beanDefinition的過程委托給BeanDefinitionParserDelegate執行個體對象來完成
    BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);

    // Default implementation is empty.
    // Subclasses can override this method to convert custom elements into standard Spring bean definitions
    preProcessXml(root);
    // 核心方法,代理
    parseBeanDefinitions(root, delegate);
    postProcessXml(root);
}
           

上面出現的BeanDefinitionParserDelegate類非常非常重要(需要了解代理技術,如JDK動态代理、cglib動态代理等)。Spirng BeanDefinition的解析就是在這個代理類下完成的,此類包含了各種對符合Spring Bean語義規則的處理,比如<bean></bean>、<import></import>、<alias><alias/>等的檢測。

parseBeanDefinitions(root, delegate)方法如下:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            // 周遊所有節點,做對應解析工作
            // 如周遊到<import>标簽節點就調用importBeanDefinitionResource(ele)方法對應處理
            // 周遊到<bean>标簽就調用processBeanDefinition(ele,delegate)方法對應處理
            for (int i = 0; i < nl.getLength(); i++) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element) node;
                    if (delegate.isDefaultNamespace(ele)) {
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        //對應使用者自定義節點處理方法
                        delegate.parseCustomElement(ele); 
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        // 解析<import>标簽
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            importBeanDefinitionResource(ele);
        }
        // 解析<alias>标簽
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            processAliasRegistration(ele);
        }
        // 解析<bean>标簽,最常用,過程最複雜
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            processBeanDefinition(ele, delegate);
        }
        // 解析<beans>标簽
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }
           

這裡針對常用的<bean>标簽中的方法做簡單介紹,其他标簽的加載方式類似:

/**
     * Process the given bean element, parsing the bean definition
     * and registering it with the registry.
     */
    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        // 該對象持有beanDefinition的name和alias,可以使用該對象完成beanDefinition向容器的注冊
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // 注冊最終被修飾的bean執行個體,下文注冊beanDefinition到容器會講解該方法
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
            }
            catch (BeanDefinitionStoreException ex) {
                getReaderContext().error("Failed to register bean definition with name '" +
                        bdHolder.getBeanName() + "'", ele, ex);
            }
            // Send registration event.
            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }
    }
           

parseBeanDefinitionElement(Element ele)方法會調用parseBeanDefinitionElement(ele, null)方法,并将值傳回BeanDefinitionHolder類對象,這個方法将會對給定的<bean> 簽進行解析,如果在解析<bean>标簽的過程中出現錯誤則傳回null。

需要強調一下的是parseBeanDefinitionElement(ele, null)方法中産生了一個抽象類型的BeanDefinition執行個體,這也是我們首次看到直接定義BeanDefinition的地方,這個方法裡面會将<bean>标簽中的内容解析到BeanDefinition中,之後再對BeanDefinition進行包裝,将它與beanName,Alias等封裝到BeanDefinitionHolder 對象中,該部分源碼如下:

public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
        return parseBeanDefinitionElement(ele, null);
    }

    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
        String id = ele.getAttribute(ID_ATTRIBUTE);
        String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
            ...(略)
        String beanName = id;
           ...(略)
        // 從上面按過程走來,首次看到直接定義BeanDefinition !!!
        // 該方法會對<bean>節點以及其所有子節點如<property>、<List>、<Set>等做出解析,具體過程本文不做分析(太多太長)
        AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
        if (beanDefinition != null) {
            if (!StringUtils.hasText(beanName)) {
                ...(略)
            }
            String[] aliasesArray = StringUtils.toStringArray(aliases);
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
        }

        return null;
    }
           

将BeanDefiniton注冊到容器中

最終Bean配置會被解析成BeanDefinition并與beanName,Alias一同封裝到BeanDefinitionHolder類中, 之後beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition()),注冊到DefaultListableBeanFactory.beanDefinitionMap中。之後用戶端如果要擷取Bean對象,Spring容器會根據注冊的BeanDefinition資訊進行執行個體化。

BeanDefinitionReaderUtils類:
 public static void registerBeanDefinition(
            BeanDefinitionHolder bdHolder, BeanDefinitionRegistry beanFactory) throws BeansException {

        // Register bean definition under primary name.
        String beanName = bdHolder.getBeanName();
     // 注冊beanDefinition!!!
        beanFactory.registerBeanDefinition(beanName, bdHolder.getBeanDefinition());

        // 如果有别名的話也注冊進去,Register aliases for bean name, if any.
        String[] aliases = bdHolder.getAliases();
        if (aliases != null) {
            for (int i = 0; i < aliases.length; i++) {
                beanFactory.registerAlias(beanName, aliases[i]);
            }
        }
    }
           

DefaultListableBeanFactory實作了上面調用BeanDefinitionRegistry接口的 registerBeanDefinition( beanName, bdHolder.getBeanDefinition())方法,這一部分的主要邏輯是向DefaultListableBeanFactory對象的beanDefinitionMap中存放beanDefinition,當初始化容器進行bean初始化時,在bean的生命周期分析裡必然會在這個beanDefinitionMap中擷取beanDefition執行個體,有機會成文分析一下bean的生命周期,到時可以分析一下如何使用這個beanDefinitionMap。

registerBeanDefinition( beanName, bdHolder.getBeanDefinition() )方法具體方法如下:

/** Map of bean definition objects, keyed by bean name */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {

        Assert.hasText(beanName, "Bean name must not be empty");
        Assert.notNull(beanDefinition, "Bean definition must not be null");

        if (beanDefinition instanceof AbstractBeanDefinition) {
            try {
                ((AbstractBeanDefinition) beanDefinition).validate();
            }
            catch (BeanDefinitionValidationException ex) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Validation of bean definition failed", ex);
            }
        }
    
     // beanDefinitionMap是個ConcurrentHashMap類型資料,用于存放beanDefinition,它的key值是beanName
        Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
        if (oldBeanDefinition != null) {
            if (!this.allowBeanDefinitionOverriding) {
                throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
                        "Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
                        "': there's already [" + oldBeanDefinition + "] bound");
            }
            else {
                if (logger.isInfoEnabled()) {
                    logger.info("Overriding bean definition for bean '" + beanName +
                            "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
                }
            }
        }
        else {
            this.beanDefinitionNames.add(beanName);  
        }
     // 将擷取到的BeanDefinition放入Map中,容器操作使用bean時通過這個HashMap找到具體的BeanDefinition
        this.beanDefinitionMap.put(beanName, beanDefinition);
        // Remove corresponding bean from singleton cache, if any.
        // Shouldn't usually be necessary, rather just meant for overriding
        // a context's default beans (e.g. the default StaticMessageSource
        // in a StaticApplicationContext).
        removeSingleton(beanName);
    }
           

小結

定位:RecourceLoader接口的實作類通過getResource(資源參數)擷取Resource對象。

載入:loadBeanDefinitions(DefaultListableBeanFactory beanFactory) ,建立一個與容器對應的BeanDefinitionReader執行個體對象,然後将生成的BeanDefintionReader執行個體作為參數傳入loadBeanDefintions(XmlBeanDefinitionReader)。

1.BeanDefinitionReader接口的實作類加載Resource到reader中:先将resource包裝為EncodeResource類型,再将resource資源檔案的内容讀入到document中。

2.BeanDefinitionParserDelegate類包含了各種對符合Spring Bean語義規則的處理,将document中的<bean>标簽中的内容解析到BeanDefinition中,之後再對BeanDefinition進行包裝,将它與beanName,Alias等封裝到BeanDefinitionHolder 對象中。

注冊:DefaultListableBeanFactory實作了BeanDefinitionRegistry接口,完成BeanDefinition向容器的注冊。通過registerBeanDefinition( beanName, bdHolder.getBeanDefinition())方法,向DefaultListableBeanFactory對象的beanDefinitionMap中存放BeanDefinition。