Spring系列之IOC容器

1、概述

  IOC容器就是具備依賴注入功能的容器,IOC容器負責實例化、定位、配置應用程序中的對象及創建這些對象之間的依賴。應用程序無需直接在代碼中new 相關的對象,應用程序由IOC容器進行組裝。在Spring中BeanFactory是IOC容器的實際表明者。node

  由IOC容器管理的那些組成你應用程序的對象咱們就叫它Bean,Bean就是由Spring容器初始化、裝配及管理的對象。框架

  Spring提供了兩種容器:BeanFactory和ApplicationContext。ide

  BeanFactory:基礎類型IOC容器,提供完整的IoC服務,默認採用延遲初始化策略,也就是隻有客戶端對象須要訪問容器中的某個受管理對象的時候,纔對該受管理對象進行初始化以及依賴注入操做。工具

  ApplicationContext:是在BeanFactory的基礎上構建,除了擁有BeanFactory的全部支持,還提供了其餘高級特性,例如事件發佈、國際化支持等。ApplicationContext所管理的對象,在容器啓動後默認所有初始化並綁定完成,因此相對於BeanFactory,它要求更多的資源。post

2、BeanFactory的對象註冊與依賴綁定

  XML配置格式是Spring支持的最完整、功能最強大的配置方式。ui

  一、配置文件封裝

  Spring對其內部使用到的資源實現了本身的抽象結構:Resource接口來封裝底層資源。this

  public interface InputStreamSource
  {
    InputStream getInputStream() throws IOException;
  }

  InputStreamResource封裝任何返回InputStream的類,好比File、ClassPath下的資源和Byte Array等。編碼

public interface Resource extends InputStreamSource
 {
    boolean exists();
    boolean isReadable();
    boolean isOpen();
    URL getURL() throws IOException;
    URI getURI() throws IOException;
    File getFile() throws IOException;
    long contentLength() throws IOException;
    long lastModified() throws IOException;
    Resource createRelative(String relativePath) throws IOException;
    String getFilename();
    String getDescription();
}

  Resource接口抽象了全部Spring內部使用到的底層資源:File、URL、classPath等。對不一樣來源的資源文件都有相應的Resource實現:文件(FileSystemResource)、ClassPath資源(ClassPathResource)、URL資源(URLResource)等。相關類圖以下:url

  

  在平常的開發中,咱們能夠直接使用Spring提供的類來進行資源的加載,好比:spa

public class Test
{
    public static void main(String[] args) throws IOException
    {
        BeanFactory beanFactory=new XmlBeanFactory(new ClassPathResource("xmlBeanFactory.xml"));
        MyTestBean myTestBean=(MyTestBean) beanFactory.getBean("myTestBean");
        myTestBean.fun();
        //加載ClassPath資源
        Resource resource=new ClassPathResource("test.txt"); 
        InputStream inputStream=resource.getInputStream();
        BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
        System.out.println(reader.readLine());
        //加載File文件資源
        Resource resource=new FileSystemResource("D:\\fileResourceTest.txt");
        InputStream inputStream=resource.getInputStream();
        BufferedReader reader=new BufferedReader(new InputStreamReader(inputStream));
        String str;
        while((str=reader.readLine())!=null)
            System.out.println(str);    
    }
}

   二、加載Bean

  當經過Resource相關類完成了對配置文件封裝後,配置文件的讀取工做就全權交給XmlBeanDifinitionReader來處理了。XmlBeanDifinitionReader負責讀取Spring指定格式的XML配置文件並解析,以後將解析後的文件內容映射到相應的BeanDefinition,並加載到相應的BeanDefinitionRegistry中。這時,整個BeanFactory就能夠放給客戶端使用了。

  

  整個資源的加載過程很複雜,參考下面的時序圖

  

  整個流程大體要通過如下幾個步驟:

  1. 封裝資源文件。進入XmlBeanDefinitionReader後,首先使用EncodedResource類對resource進行封裝。
  2. 獲取輸入流,從Resource中獲取對應的InputStream並構造。
  3. 經過構造的InputStream實例和resource實例繼續調用doLoadBeanDefinitions。

  首先對傳入的Resource資源作封裝,目的是考慮到Resource可能存在編碼要求的狀況,其次,經過SAX讀取XML文件的方式來準備InputSource對象,最後將準備的數據經過參數傳入真正的核心處理部分doLoadBeanDefinitions(inputSource,encodedResource.getResource())。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            /*
             * 加載XML文件,並獲得相應的、Document
             * 根據返回的Document註冊Bean信息
             */
            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的代碼中,執行下列代碼

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception
{
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, getValidationModeForResource(resource), isNamespaceAware()); }

  DocumentLoader只是一個接口,這裏真正調用的是DefaultDocumentLoader。

@Override
    public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
            ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

        DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
        if (logger.isDebugEnabled()) {
            logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
        }
        DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
        return builder.parse(inputSource);
    }

  與SAX解析XML文檔的思路一致,這裏首先建立DocumentBuilderFactory,再經過DocumentBuilderFactory建立DocumentBuilder,進而解析InputSource來返回Document對象。DocumentLoader還涉及到驗證模式的讀取。

  備註:

  驗證模式,XML文件的驗證模式保證了XML文件的正確性,比較經常使用的有兩種,DTD和XSD。

  DTD:文檔定義類型,屬於XML文件組成的一部分,是一種保證XML文檔格式正確的有效方法,能夠經過比較XML文檔和DTD文件來看文檔是否符合規範,元素和標籤是否正確。一個DTD文件包含:元素的定義規則,元素間關係的定義規則,元素可以使用的屬性,可以使用的實體或符號規則。

  XSD:即XML schema。描述了XML文檔的結構,可使用一個指定的XML schema來驗證某個XML文檔,以堅持該XML文檔是否符合其要求。

  Spring經過getValidationModeForResource方法來獲取對對應資源的驗證模式:

protected int getValidationModeForResource(Resource resource) {
        int validationModeToUse = getValidationMode();
        //若是手動指定了驗證模式則使用指定的驗證模式
        if (validationModeToUse != VALIDATION_AUTO) {
            return validationModeToUse;
        }
        //若是未指定,則使用自動檢測
        int detectedMode = detectValidationMode(resource);
        if (detectedMode != VALIDATION_AUTO) {
            return detectedMode;
        }
        return VALIDATION_XSD;
    }

  當把文件轉換爲Document後,接下來就是提取並註冊bean了,也就是執行registerBeanDefinitions(doc, resource)方法。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException 
    {
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        //將環境變量設置其中
        documentReader.setEnvironment(getEnvironment());
        //記錄統計前BeanDefinition的加載個數
        int countBefore = getRegistry().getBeanDefinitionCount();
        //加載和註冊bean
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        //記錄本次加載的BeanDefinition的個數
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }
    

  進入registerBeanDefinitions方法:

@Override
    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
        this.readerContext = readerContext;
        logger.debug("Loading bean definitions");
        Element root = doc.getDocumentElement();
        doRegisterBeanDefinitions(root);
    }

  doRegisterBeanDefinitions()方法就是真正開始解析了。

    protected void doRegisterBeanDefinitions(Element root)
    {
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = createDelegate(getReaderContext(), root, parent);
        // 處理profile屬性
        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;
    }

  處理了profile後就進行XML讀取了,跟蹤代碼進入parseBeanDefinitions方法。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        //對beans的處理
        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)) {
                        //對bean的處理
                        parseDefaultElement(ele, delegate);
                    }
                    else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

3、ApplicationContext

  Spring爲基本的BeanFactory類型容器提供了XMLBeanFactory實現,相應地,它也爲ApplicationContext類型容器提供瞭如下幾個實現:

  • FileSystemApplicationContext:從文件系統加載bean定義以及相關資源的實現
  • ClassPathXmlApplicationContext:從CLASSPATH加載bean定義以及相關資源的實現
  • XMLWebApplicationContext:用於Web應用程序的實現

  一、統一資源加載策略

  Spring框架內部使用Resource接口做爲全部資源的抽象和訪問接口,上面已有接觸,其中ClassPathResource就是Resource的一個特定類型的實現,表明位於Classpath中的資源。有了資源,還須要ResourceLoader去查找和定位這些資源。

  ResourceLoader有一個默認的實現類DefaultResourceLoader,實現代碼以下:

@Override
    public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
        if (location.startsWith("/")) {
            return getResourceByPath(location);
        }
        else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
        }
        else {
            try {
                // Try to parse the location as a URL...
                URL url = new URL(location);
                return new UrlResource(url);
            }
            catch (MalformedURLException ex) {
                // No URL -> resolve as resource path.
                return getResourceByPath(location);
            }
        }
    }

  處理邏輯以下:

  1. 首選檢查資源路徑是否以classpath:前綴打頭,若是是,則嘗試構造ClassPathResource類型資源並返回
  2. 不然,嘗試經過url,根據資源路徑來定位資源
  3. 若是尚未,則委派getResourceByPath方法來定位

  ResourcePatternResolver是ResourceLoader的擴展,ResourceLoader每次只能根據資源路徑返回肯定的單個Resource實例,而ResourcePatternResolver則能夠根據指定的資源路徑匹配模式,每次返回多個Resource實例。

  

  ApplicationContext繼承了ResourcePatternResolver,也就間接實現了ResourceLoader接口,這就是ApplicationContext支持Spring內統一資源加載策略的真相。

4、IOC容器功能實現

  綜上,IOC容器會以某種方式加載Configuration Metadata(一般是XMl格式的配置信息),而後根據這些信息綁定整個系統的對象,最終組裝成一個可用的基於輕量級容器的應用系統。

  基本上能夠分爲兩個階段,容器的啓動階段和Bean實例化階段。

  一、容器啓動階段

  容器啓動伊始,首先會經過某種途徑加載Configuration Metadata,大部分狀況下須要依賴某些工具類(BeanDefinitionReader)對加載的Configuration Metadata進行解析和分析,並將分析後的信息編組爲相應的BeanDefinition,最後把這些保存了bean定義必要信息的BeanDefinition,註冊到相應的BeanDefinitionRegistry,這樣容器的啓動工做就完成了。

  二、Bean實例化階段

  通過第一階段,如今全部的bean定義信息都經過BeanDefinition的方式註冊到BeanDefinitionRegistry中,當某個請求方經過容器的getBean方法明確的請求某個對象,或者因依賴關係容器須要隱式的調用getBean方法時,就會觸發第二個階段的活動。

  該階段,容器會首先檢查所請求的對象以前是否已經初始化,若是沒有,則會根據註冊的BeanDefinition所提供的信息實例化請求對象,併爲其注入依賴,若是該對象實現了某些回調接口,也會根據回調接口的要求來裝配它。當該對象裝配完成以後,容器就會當即將其返回請求方使用。

相關文章
相關標籤/搜索