spring源碼閱讀(一)--IOC容器的實現:資源文件的加載過程

引言

上一篇從總體瞭解的spring IOC容器的過程–大致可以分爲資源定位,解析bean,初始化bean三個過程,那我們就分析一下spring如何對資源定位以及加載的。

加載過程

我們還是以ClassPathXmlApplicationContext爲例,上篇說到,資源的加載是由AbstractApplicationContext的obtainFreshBeanFactory()方法開始的,這個方法最終返回了一個包含bean定義信息的BeanFactory。

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    refreshBeanFactory();
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (logger.isDebugEnabled()) {
        logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
    }
    return beanFactory;
}

而具體又是在refreshBeanFactory()中載入和解析的,refreshBeanFactory()是一個抽象方法,交由子類AbstractRefreshableApplicationContext來實現,如下:

@Override
protected final void refreshBeanFactory() throws BeansException {
    if (hasBeanFactory()) {
        destroyBeans();
        closeBeanFactory();
    }
    try {
        //創建一個空的BeanFactory
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId());
        //對BeanFactory做一些設置
        customizeBeanFactory(beanFactory);
        //載入資源並解析bean定義
        loadBeanDefinitions(beanFactory);
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    }
    catch (IOException ex) {
        throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
        }
    }

我們接着進入loadBeanDefinitions()方法來看具體的過程,同樣loadBeanDefinitions()也是抽象方法,具體交由AbstractXmlApplicationContext來實現,如下:

@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    // 創建一個讀取器
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

    beanDefinitionReader.setEnvironment(this.getEnvironment());
    //因爲AbstractXmlApplicationContext繼承了DefaultResourceLoader,所以資源加載器設置爲了自身
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

    //允許子類對beanDefinitionReader做一些定製
    initBeanDefinitionReader(beanDefinitionReader);
    //具體載入過程
    loadBeanDefinitions(beanDefinitionReader);
}

進入loadBeanDefinitions()

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }

        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            //具體載入過程
            reader.loadBeanDefinitions(configLocations);
        }
    }

這裏講具體的載入過程叫給XmlBeanDefinitionReader來實現。進入reader.loadBeanDefinitions()

@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(location)

@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
    return loadBeanDefinitions(location, null);
}

進入loadBeanDefinitions(location, null);

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
        //獲取資源加載器
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        }

        if (resourceLoader instanceof ResourcePatternResolver) {
            try {
                // 定位資源 
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                // 解析
                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 {
            // 通過絕對路徑定位資源
            Resource resource = resourceLoader.getResource(location);
            int loadCount = loadBeanDefinitions(resource);
            if (actualResources != null) {
                actualResources.add(resource);
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
            }
            return loadCount;
        }
    }

我們回顧一下整體的流程
這裏寫圖片描述

資源定位

這裏我們關注一下具體是怎麼定位資源的
通過((ResourcePatternResolver) resourceLoader).getResources(location)來獲取到對應的資源,同樣org.springframework.core.io.support.ResourcePatternResolver#getResources是個抽象方法,這裏由於我們使用的是ClassPathXmlApplicationContext,所以由PathMatchingResourcePatternResolver來具體實現:

@Override
public Resource[] getResources(String locationPattern) throws IOException {
    Assert.notNull(locationPattern, "Location pattern must not be null");
    if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
        //如果路徑以classpath*: 開頭並且路徑中含有通配符,將會遞歸獲取資源
        if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {

            return findPathMatchingResources(locationPattern);
        }
        //以classpath*開頭,將會從本地和所有的jar包中獲取
        else {
            return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
        }
    }
    else {
        //去除前綴
        int prefixEnd = locationPattern.indexOf(":") + 1;
        if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
            // a file pattern
            return findPathMatchingResources(locationPattern);
        }
        else {
            //直接獲取
            return new Resource[] {getResourceLoader().getResource(locationPattern)};
        }
    }
}

通過這個方法,將路徑轉換爲了spring可以使用的Resource對象。具體實現可以參照源碼,因爲代碼比較細碎,這裏不再多講。

資源文件的解析

在org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.Resource…)方法中,定義瞭解析的邏輯。

@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
    Assert.notNull(resources, "Resource array must not be null");
    int counter = 0;
    for (Resource resource : resources) {
        counter += loadBeanDefinitions(resource);
    }
    return counter;
}

對於資源文件,循環解析。而loadBeanDefinitions(resource)交由子類實現。
以XmlBeanDefinitionReader爲例:

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

接着進入doLoadBeanDefinitions

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,至此,spring如何將path解析爲spirng可以使用的Document已經完成了。由於篇幅有限,可能有許多細節的方法沒有講到。但是掌握了成體脈絡,接下來對於看細節相信也不是什麼難事。
下一篇我們將分析spring是如何將Document解析爲BeanDefinition並註冊進BeanFactory中的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章