Spring5源碼分析(二) IOC 容器的初始化(二)

  承接上一篇文章繼續分析FileSystemXmlApplicationContextIOC容器建立的流程。java

2.5 AbstractBeanDefinitionReader讀取Bean定義資源,在其抽象父類AbstractBeanDefinitionReader 中定義了載入過程。

AbstractBeanDefinitionReader 的 loadBeanDefinitions 方法源碼以下:數據結構

//重載方法,調用下面的loadBeanDefinitions(String, Set<Resource>);方法
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(location, null);
}

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
    //獲取在IoC容器初始化過程當中設置的資源加載器
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException(
                "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
    }

    if (resourceLoader instanceof ResourcePatternResolver) {
        // Resource pattern matching available.
        try {
            //將指定位置的Bean定義資源文件解析爲Spring IOC容器封裝的資源
            //加載多個指定位置的Bean定義資源文件
            Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
            //委派調用其子類XmlBeanDefinitionReader的方法,實現加載功能
            int loadCount = loadBeanDefinitions(resources);
            if (actualResources != null) {
                for (Resource resource : resources) {
                    actualResources.add(resource);
                }
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
            }
            return loadCount;
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "Could not resolve bean definition resource pattern [" + location + "]", ex);
        }
    }
    else {
        // Can only load single resources by absolute URL.
        //將指定位置的Bean定義資源文件解析爲Spring IOC容器封裝的資源
        //加載單個指定位置的Bean定義資源文件
        Resource resource = resourceLoader.getResource(location);
        //委派調用其子類XmlBeanDefinitionReader的方法,實現加載功能
        int loadCount = loadBeanDefinitions(resource);
        if (actualResources != null) {
            actualResources.add(resource);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
        }
        return loadCount;
    }
}

//重載方法,調用loadBeanDefinitions(String);
@Override
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
    Assert.notNull(locations, "Location array must not be null");
    int counter = 0;
    for (String location : locations) {
        counter += loadBeanDefinitions(location);
    }
    return counter;
}

loadBeanDefinitions(Resource…resources)方法和上面分析的3個方法相似,一樣也是調用XmlBeanDefinitionReader 的 loadBeanDefinitions 方法。從對 AbstractBeanDefinitionReader 的 loadBeanDefinitions 方法源碼分析能夠看出該方法作了如下兩件事:
首先,調用資源加載器的獲取資源方法resourceLoader.getResource(location),獲取到要加載的資源。其次,真正執行加載功能是其子類XmlBeanDefinitionReader 的 loadBeanDefinitions 方法。
這裏寫圖片描述ide

看到上面的 ResourceLoader與ApplicationContext的繼承系圖,能夠知道其實際調用的是DefaultResourceLoader中的getSource()方法定位Resource , 由於FileSystemXmlApplicationContext自己就是DefaultResourceLoader 的實現類,因此此時又回到了 FileSystemXmlApplicationContext 中來。源碼分析

2.6 AbstractBeanDefinitionReader讀取Bean定義資源,在其抽象父類AbstractBeanDefinitionReader 中定義了載入過程。

//獲取Resource的具體實現方法
@Override
public Resource getResource(String location) {
    Assert.notNull(location, "Location must not be null");

    for (ProtocolResolver protocolResolver : this.protocolResolvers) {
        Resource resource = protocolResolver.resolve(location, this);
        if (resource != null) {
            return resource;
        }
    }
    //若是是類路徑的方式,那須要使用ClassPathResource 來獲得bean 文件的資源對象
    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 方式,使用UrlResource 做爲bean 文件的資源對象
            URL url = new URL(location);
            return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
        }
        catch (MalformedURLException ex) {
            // No URL -> resolve as resource path.
            //若是既不是classpath標識,又不是URL標識的Resource定位,則調用
            //容器自己的getResourceByPath方法獲取Resource
            return getResourceByPath(location);
        }
    }
}

FileSystemXmlApplicationContext 容器提供了 getResourceByPath 方法的實現,就是爲了處理既
不是 classpath 標識,又不是 URL 標識的 Resource 定位這種狀況。ui

@Override
protected Resource getResourceByPath(String path) {
    if (path.startsWith("/")) {
    path = path.substring(1);
    }
    //這裏使用文件系統資源對象來定義 bean 文件
    return new FileSystemResource(path);
}

這樣代碼就回到了FileSystemXmlApplicationContext中來,他提供了FileSystemResource 來完成從文件系統獲得配置文件的資源定義。這樣,就能夠從文件系統路徑上對IOC配置文件進行加載,固然咱們能夠按照這個邏輯從任何地方加載,在Spring中咱們看到它提供的各類資源抽象,好比
ClassPathResource,URLResource,FileSystemResource等來供咱們使用。上面咱們看到的是定位Resource的一個過程,而這只是加載過程的一部分.this

2.7 XmlBeanDefinitionReader 加載Bean的定義資源

  繼續回到 XmlBeanDefinitionReader的loadBeanDefinitions(Resource …)方法看到表明bean 文件的資源定義之後的載入過程。編碼

//XmlBeanDefinitionReader加載資源的入口方法
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    //將讀入的XML資源進行特殊編碼處理
    return loadBeanDefinitions(new EncodedResource(resource));
}


//這裏是載入XML形式Bean定義資源文件方法
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    .......
    try {
        //將資源文件轉爲InputStream的IO流
        InputStream inputStream = encodedResource.getResource().getInputStream();
        try {
            //從InputStream中獲得XML的解析源
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            //這裏是具體的讀取過程
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
        }
        finally {
            //關閉從Resource中獲得的IO流
            inputStream.close();
        }
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException(
                "IOException parsing XML document from " + encodedResource.getResource(), ex);
    }
   ...........
}

//從特定XML文件中實際載入Bean定義資源的方法
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    try {
        //將XML文件轉換爲DOM對象,解析過程由documentLoader實現
        Document doc = doLoadDocument(inputSource, resource);
        //這裏是啓動對Bean定義解析的詳細過程,該解析過程會用到Spring的Bean配置規則
        return registerBeanDefinitions(doc, resource);
    }
    catch (BeanDefinitionStoreException ex) {
        throw ex;
    }
   .....
}

經過源碼分析,載入Bean定義資源文件的最後一步是將Bean定義資源轉換爲Document對象,該過程由 documentLoader 實現.url

2.8 DocumentLoader 將Bean定義資源轉換爲Document對象

DocumentLoader 將 Bean 定義資源轉換成 Document 對象的源碼以下:spa

//使用標準的JAXP將載入的Bean定義資源轉換成document對象
@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);
    //解析Spring的Bean定義資源
    return builder.parse(inputSource);
}

protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
            throws ParserConfigurationException {

    //建立文檔解析工廠
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(namespaceAware);

    //設置解析XML的校驗
    if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
        factory.setValidating(true);
        if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
            // Enforce namespace aware for XSD...
            factory.setNamespaceAware(true);
            try {
                factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
            }
            catch (IllegalArgumentException ex) {
                ParserConfigurationException pcex = new ParserConfigurationException(
                        "Unable to validate using XSD: Your JAXP provider [" + factory +
                        "] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
                        "Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
                pcex.initCause(ex);
                throw pcex;
            }
        }
    }

    return factory;
}

該解析過程調用 JavaEE 標準的 JAXP 標準進行處理。
至此 Spring IOC 容器根據定位的Bean定義資源文件,將其加載讀入並轉換成爲 Document 對象過程完成。接下來咱們要繼續分析 Spring IOC 容器將載入的 Bean定義資源文件轉換爲Document對象以後,是如何將其解析爲 Spring IOC 管理的 Bean 對象並將其註冊到容器中的。debug

2.9 XmlBeanDefinitionReader析載入的Bean定義資源文件

XmlBeanDefinitionReader 類中的 doLoadBeanDefinitions方法是從特定 XML 文件中實際載入Bean定義資源的方法,該方法在載入Bean定義資源以後將其轉換爲 Document 對象,接下來調用registerBeanDefinitions 啓 動SpringIOC容器對Bean定義的解析過程,registerBeanDefinitions方法源碼以下:

//按照Spring的Bean語義要求將Bean定義資源解析並轉換爲容器內部數據結構
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    //獲得BeanDefinitionDocumentReader來對xml格式的BeanDefinition解析
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    //得到容器中註冊的Bean數量
    int countBefore = getRegistry().getBeanDefinitionCount();
    //解析過程入口,這裏使用了委派模式,BeanDefinitionDocumentReader只是個接口,
    //具體的解析實現過程有實現類DefaultBeanDefinitionDocumentReader完成
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    //統計解析的Bean數量
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

Bean 定義資源的載入解析分爲如下兩個過程:
  首先,經過調用 XML 解析器將 Bean 定義資源文件轉換獲得 Document 對象,可是這些 Document對象並無按照Spring的Bean規則進行解析。這一步是載入的過程.
  其次,在完成通用的 XML 解析以後,按照 Spring 的 Bean 規則對 Document 對象進行解析。按照 Spring 的 Bean 規則對 Document 對象解析的過程是在接口BeanDefinitionDocumentReader的實現類 DefaultBeanDefinitionDocumentReader 中實現的。

因爲IOC 容器的初始化內容比較多一次文章沒法寫完,因此分了五篇進行講解此篇爲第二篇。

文檔有參考其餘資料,若是問題請聯繫我,進行刪除!

相關文章
相關標籤/搜索