1. 程式人生 > >Spring原理(一)IoC容器的初始化過程之BeanFactory

Spring原理(一)IoC容器的初始化過程之BeanFactory

  1. IoC容器的初始化過程
    IoC容器的啟動過程包括BeanDefinition的Resource定位、載入和註冊三個基本過程。
    但spring是把這三個過程分開的,並用不同的模組來完成,比如ResourceLoader、
    BeanDefinitionReader、這種設計具有很高的靈活性,使用者可以根據不同的需要組合出適合自己的初始
    化過程。
    第一個過程是Resource定位。即BeanDefinition資源的定位,spring定義了一個Resource介面來定
    義資源,ResourceLoader負責資源的定位。Spring支援多種Resource如下圖,
    這裡寫圖片描述

    如果把IoC容器的初始化過程比作一個人在沙漠裡找水的話,Resource定位就相當於找到沙漠中的水源。
    第二個過程是BeanDefinition的載入。這個過程就是把使用者解除安裝配置檔案中的bean表示成IoC容器內
    的資料結構,即BeanDefinition。通過BeanDefinition這個資料結構IoC容器能夠方便的對POJO對
    象也就是Bean進行管理。BeanDefinition的載入分為兩個過程,首先通過呼叫XML的解析器得到
    Document物件,但這些Document物件並沒有按照spring定義的bean的規則進行解析;然後
    DocumentReader按照spring定義的bean的規則進行解析,預設的DocumentReader是
    DefaultBeanDefinitionDocumentReader。
    第三個過程是向IoC容器註冊這些BeanDefinition。載入的BeanDefinition最終是通過一個HashMap來持有的,因此註冊也就是把解析得到的BeanDefinition設定到HashMap中去。通過實現BeanDefinitionRegistry介面的方法registerBeanDefinition來註冊BeanDefinition。
  2. 原始碼分析
    這裡以程式設計方式初始化DefaultListableBeanFactory為例。
    1)Resource定位
    //這裡以類路徑型別的資源為例,根據上面介紹還可以用其他型別的資源
    ClassPathResource res = new ClassPathResource(“applicationContext.xml”);
    這裡的Resource並不能被IoC容器直接使用,需要解析成IOC容器能夠理解的資料型別,即
    BeanDefinition,spring通過BeanDefinitionReader來對Resource資訊進行處理。因為
    DefaultListableBeanFactory從名字上可以看出來,它是一個純粹的IoC容器(這裡注意和
    applicationContext的區別,即非純粹的IoC容器,或者叫高階IoC容器,在BeanFactory的基礎上附加
    了很多功能,比如這裡設定的讀取器),因此需要為它配置特定的讀取器。
    2)Resource載入和解析
    第一步找到了水源,這一步就要實體檢視一下,水源有很多種,特別是在沙漠,有點水就可以救命啊,什麼仙人掌上的露珠,只剩井底小水坑的枯井,沙漠小綠洲,甚至神祕的地下河(電影裡的情節),根據不同的水源把水取到手才是最重要的,這裡載入Resource非常類似。
    這裡使用程式設計的方式初始化一個純粹的IoC容器,即DefaultListableBeanFactory,並把它設定到XmlBeanDefinitionReader 中,因為DefaultListableBeanFactory實現了BeanDefinitionRegistry的方法registerBeanDefinition,這個方法用於註冊BeanDefinition,在第三步會詳細介紹。
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);

XmlBeanDefinitionReader的loadBeanDefinitions是實現了介面BeanDefinitionReader的
loadBeanDefinitions(Resource resource)方法(這裡有一個模板的設計模式,但使用這種程式設計的方式初始化純粹的IoC容器並未體現,下文分析applicationContext初始化的時候會進一步說明)

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
}

然後呼叫下面XmlBeanDefinitionReader自己的loadBeanDefinitions

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

        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<EncodedResource>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
            "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
    }

doLoadDocument獲取XML檔案的Document物件,這裡使用的是預設的DocumentLoader

       private DocumentLoader documentLoader = new DefaultDocumentLoader();

registerBeanDefinitions方法啟動對Document的解析按照spring定義的bean規則(看方法名字容易產生歧義,認為這是註冊BeanDefinition的方法,其實不然,個人感覺這個命名不太合適,很容易產生誤解,特別是對第一次讀原始碼的菜鳥)

            protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            Document doc = doLoadDocument(inputSource, resource);
            return registerBeanDefinitions(doc, resource);
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (SAXParseException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
        }
        catch (SAXException ex) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(),
                    "XML document from " + resource + " is invalid", ex);
        }
        catch (ParserConfigurationException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Parser configuration exception parsing XML from " + resource, ex);
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "IOException parsing XML document from " + resource, ex);
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(resource.getDescription(),
                    "Unexpected exception parsing XML document from " + resource, ex);
        }
    }

整個Document物件的解析都是由BeanDefinitionDocumentReader 負責,createBeanDefinitionDocumentReader建立解析BeanDefinition的文件讀取器,具體的解析過程由registerBeanDefinitions方法完成。

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        documentReader.setEnvironment(getEnvironment());
        int countBefore = getRegistry().getBeanDefinitionCount();
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

這裡設定了一個readerContext,用於最後一步註冊BeanDefinition,會在第三步詳細說明
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        doRegisterBeanDefinitions(root);
    }

        protected void doRegisterBeanDefinitions(Element root) {
        // Any nested <beans> elements will cause recursion in this method. In
        // order to propagate and preserve <beans> default-* attributes correctly,
        // keep track of the current (parent) delegate, which may be null. Create
        // the new (child) delegate with a reference to the parent for fallback purposes,
        // then ultimately reset this.delegate back to its original (parent) reference.
        // this behavior emulates a stack of delegates without actually necessitating one.
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);

        if (this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
                        profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
                if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    return;
                }
            }
        }

        preProcessXml(root);
        parseBeanDefinitions(root, this.delegate);
        postProcessXml(root);

        this.delegate = parent;
    }

parseDefaultElement真正對Document物件的元素進行解析

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();
            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);
        }
    }    

下面是解析不同的spring配置檔案不同標籤的邏輯

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            importBeanDefinitionResource(ele);
        }
        else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            processAliasRegistration(ele);
        }
        else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            processBeanDefinition(ele, delegate);
        }
        else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
            // recurse
            doRegisterBeanDefinitions(ele);
        }
    }

在方法parseBeanDefinitionElement裡會去例項化配置檔案中配置的bean,注意看異常處理部分,我們平時經常遇到的Bean class not found的錯誤是在這裡暴露出來的。

public AbstractBeanDefinition parseBeanDefinitionElement(
            Element ele, String beanName, BeanDefinition containingBean) {

        this.parseState.push(new BeanEntry(beanName));

        String className = null;
        if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
            className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
        }

        try {
            String parent = null;
            if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
                parent = ele.getAttribute(PARENT_ATTRIBUTE);
            }
            AbstractBeanDefinition bd = createBeanDefinition(className, parent);

            parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
            bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));

            parseMetaElements(ele, bd);
            parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
            parseReplacedMethodSubElements(ele, bd.getMethodOverrides());

            parseConstructorArgElements(ele, bd);
            parsePropertyElements(ele, bd);
            parseQualifierElements(ele, bd);

            bd.setResource(this.readerContext.getResource());
            bd.setSource(extractSource(ele));

            return bd;
        }
        catch (ClassNotFoundException ex) {
            error("Bean class [" + className + "] not found", ele, ex);
        }
        catch (NoClassDefFoundError err) {
            error("Class that bean class [" + className + "] depends on not found", ele, err);
        }
        catch (Throwable ex) {
            error("Unexpected failure during bean definition parsing", ele, ex);
        }
        finally {
            this.parseState.pop();
        }

        return null;
    }

3)Resource註冊
一條小河出現在眼前,主角喜極而泣,奔到河邊,狂喝一頓,突然想起了快渴死的同伴,於是裝滿水壺帶回去,拯救了同伴,而且後面的行程,想喝就喝。接下來要發生的事情何其類似。
BeanDefinition載入完成之後,要把它註冊到IoC容器中,其實就是把bean物件新增到一個HashMap中,BeanDefinitionReaderUtils.registerBeanDefinition方法完成註冊。
這裡getReaderContext得到的readerContext是之前步驟中已經設定好的,即DefaultListableBeanFactory,他實現了BeanDefinitionRegistry的方法registerBeanDefinition,最終完成BeanDefinition的註冊。

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
            try {
                // Register the final decorated instance.
                **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));
        }
    }
    public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {

        // Register bean definition under primary name.
        String beanName = definitionHolder.getBeanName();
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

        // Register aliases for bean name, if any.
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String aliase : aliases) {
                registry.registerAlias(beanName, aliase);
            }
        }
    }
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {

        Assert.hasText(beanName, "Bean name must not be empty");
        Assert.notNull(beanDefinition, "BeanDefinition 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);
            }
        }

        BeanDefinition oldBeanDefinition;

        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 is already [" + oldBeanDefinition + "] bound.");
            }
            else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
                // e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
                            " with a framework-generated bean definition ': replacing [" +
                            oldBeanDefinition + "] with [" + beanDefinition + "]");
                }
            }
            else {
                if (this.logger.isInfoEnabled()) {
                    this.logger.info("Overriding bean definition for bean '" + beanName +
                            "': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
                }
            }
        }
        else {
            this.beanDefinitionNames.add(beanName);
            this.manualSingletonNames.remove(beanName);
            this.frozenBeanDefinitionNames = null;
        }
        this.beanDefinitionMap.put(beanName, beanDefinition);

        if (oldBeanDefinition != null || containsSingleton(beanName)) {
            resetBeanDefinition(beanName);
        }
    }

到此為止,最基本IoC容器的初始化的過程的脈絡已經顯現出來了。下一篇會在此基礎上介紹高階容器ApplicationContext的初始化過程。

相關推薦

Spring原理IoC容器初始過程BeanFactory

IoC容器的初始化過程 IoC容器的啟動過程包括BeanDefinition的Resource定位、載入和註冊三個基本過程。 但spring是把這三個過程分開的,並用不同的模組來完成,比如ResourceLoader、 BeanDefi

Spring解析 IOC容器

1、IOC簡介    spring框架是java web開發中的重要框架,其中IOC(Inversion of Control)控制反轉和AOP切面程式設計是spring的核心,IOC不僅僅是一個技術,同時也是一種設計思想。傳統的java開發當中,一個物件內往往需要多個物件

spring技術內幕筆記:IoC容器初始過程2- BeanDefinition的載入

Spring版本:4.3.8.RELEASEBeanDefinition載入過程,相當於把定義的BeanDefinition在IoC容器中轉換成一個Spring內部表示的資料結構的過程。IoC容器對Bean的管理和依賴注入功能的實現,就是通過對其持有的BeanDefiniti

Wine中PE格式檔案的載入:Wine初始過程

首先了解下Wine初始化過程。我們執行”wine WeChat.exe”命令,發生的過程是怎麼樣的?接下來從wine原始碼一步步分析函式呼叫過程。在loader/目錄下的原始碼編譯,由main.c生成了“wine”Linux可執行檔案;preloader.c生成了“pre-l

Spring原始碼解析--《SPRING技術內幕:深入解析Spring架構與設計原理》讀書筆記IOC容器初始過程

通過閱讀相關章節內容,Spring中IOC容器的載入中,我們需要了解下列幾個概念: Resource:是一個定位、訪問資源的抽象介面,包含了多種資源操作的基礎方法定義,如getInputStream()、exists()、isOpen()、getD

spring原始碼學習路---IOC容器初始要義bean定義載入

上章說到要帶各位去看看bean定義載入的要義,其實就是loadBeanDefinitions這個方法的具體實現步驟,下面我們跟隨這個方法去看下它到底是如何載入bean定義的。 上面是我擷取的實現了loadBeanDefinitions的類級別截圖,loadBeanDefinit

spring原始碼學習路---深度分析IOC容器初始過程

分析FileSystemXmlApplicationContext的建構函式,到底都做了什麼,導致IOC容器初始化成功。 public FileSystemXmlApplicationContext(String[] configLocations, boolean ref

Spring核心技術——IoC容器和Bean簡介

IoC容器和Bean簡介 這章包括了Spring框架對於IoC規則的實現。Ioc也同DI(依賴注入)。而物件是通過建構函式,工廠方法,或者一些Set方法來定義物件之間的依賴的。容器在建立這些Bean物件的時候同時就會注入這些依賴。這個過程是根本上的反轉了,不再

Spring入門IOC、DI

使用 splay 1.0 了解 ngs eth text 實例 sts 一、Spring介紹   Spring 是一個開源框架,是為了解決企業應用程序開發復雜性而創建的。框架的主要優勢之一就是其分層架構,分層架構允許您選擇使用哪一個組件,同時為 J2EE 應用程序開發提供集

Spring學習4IOC容器配置bean:定義與實例

dimp 工廠類 def 流程 行為 更多 多個 scrip 編譯報錯 一. IOC容器配置   1. 一些概念   (1)IOC容器:   定義:具有管理對象和管理對象之間的依賴關系的容器。   作用:應用程序無需自己創建對象,對象由IOC容器創建並組裝。BeanFac

Spring 學習——IOC 容器中 Bean 的生命週期

IOC 容器中 Bean 的生命週期方法 •Spring IOC 容器可以管理 Bean 的生命週期, Spring 允許在 Bean 生命週期的特定點執行定製的任務. •Spring IOC 容器對 Bean 的生命週期進行管理的過程: –通過構造器或工廠方法建立 Bean 例項

mmall 項目實戰項目初始

ant log 初始 post gpo using base inno ole 1.創建 數據庫 及 表 數據腳本: /* Navicat Premium Data Transfer Source Server : 182.92.82.1

Android插件原理Activity插件

ssa AS 直接 接收 hat ati 操作 運行 for Android深入四大組件系列 Android解析AMS系列 Android解析ClassLoader系列 前言 四大組件的插件化是插件化技術的核心知識點,而Activity插件化更是重中之重,Activity插

Spring原始碼解析-4、IOC容器初始

IOC容器初始化的幾個步驟 IOC容器的初始化由以下三個步驟構成: 1、BeanDefinition的Resource定位 由ResourceLoader通過統一的Resource介面來完成,Resource對不同形式的BeanDefinition有統一的介面。 比如檔案系統中的Bean

Android包管理機制 PackageInstaller的初始

前言包管理機制是Android中的重要機制,是應用開發和系統開發需要掌握的知識點之一。 包指的是Apk、jar和so檔案等等,它們被載入到Android記憶體中,由一個包轉變成可執行的程式碼,這就需要一個機制來進行包的載入、解析、管理等操作,這就是包管理機制。包管理機制由許多

android6.0 adbd深入分析adb驅動初始、讀取adb節點執行緒

adbd之前看過一次,覺得程式碼太複雜然後,又是adb client  adb server adbd交織在一起感覺看起來太累,最近專案需要把它大致看完了,梳理下,感覺從adbd能學到很多東西,在此總結下,adbd的程式碼。 我只分析我看懂了,不可能面面俱到。而且主要注重在

Spring技術內幕IOC容器的實現(01)-IOC容器初始過程

Spring IOC容器的初始化過程 Spring IOC容器的初始化過程主要包括BeanDefinition的Resouce定位/載入/註冊三個基本過程。Spring把這三個過程的實現分別放在不同的模組下,通過這樣的設計方式可以使使用者更加靈活地對這個三個過程進行裁剪和自

Spring】- IOC容器初始過程

浪費了“黃金五年”的Java程式設計師,還有救嗎? >>>   

NavMesh生成研究:體素過程

NMGen是一個開源專案,包括Java版的recast靜態網格生成程式碼,以及詳細介紹生成過程的文章。因為在閱讀過程中覺得對理解recast原理非常有用,所以決定將其翻譯在自己blog上。 原blog上完整的目錄包括: 高度域介紹(Introducti

Wine中PE格式檔案的載入:Wineserver初始過程

Wineserver的初始化過程就在thread_init函式中進行的。具體分析thread_init函式(在ntdll/thread.c中)1.     建立了TEB,PEB等資料結構。2.     通過server_init_process()與服務程序建立socket連