天天看點

spring源碼分析(二)配置檔案的解析

上一篇部落格說明了下spring是如何找到資源檔案的,classpath下的xml,最終會被解析為

ClassPathContextResource

,下面進一步分析,有了這個資源檔案之後spring是如何将其解析為BeanDefinition的

入口XmlBeanDefinitionReader.loadBeanDefinitions

最開始的入口,隻是包了下Resource

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    //EncodedResource隻是存了Resource的編碼
    return loadBeanDefinitions(new EncodedResource(resource));
}
           

1)儲存目前正在加載的資源

2)檢測是否有重複加載資源的情況

3)真正幹活的地方doLoadBeanDefinitions

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    
    //... 省略日志

    //1、儲存目前正在加載的資源
    Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet<>(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }

    //2、檢測是否有重複加載資源的情況
    if (!currentResources.add(encodedResource)) {
        throw new BeanDefinitionStoreException(
                "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    }

    InputStream inputStream = encodedResource.getResource().getInputStream();
    try {
        //document加載需要的的InputSource
        InputSource inputSource = new InputSource(inputStream);
        if (encodedResource.getEncoding() != null) {
            inputSource.setEncoding(encodedResource.getEncoding());
        }
        //3、真正幹活的地方doLoadBeanDefinitions
        return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
    }
    finally {
        inputStream.close();
    }

    //... 省略 一系列異常處理
}
           

1、這裡做的兩件事,将資源檔案解析成Document

2、解析并注冊BeanDefinitions

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    
    Document doc = doLoadDocument(inputSource, resource);
    return registerBeanDefinitions(doc, resource);
    
}

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

document的解析

XmlBeanDefinitionReader 将 Document的生成,委托給了DocumentLoader

看下預設實作 DefaultDocumentLoader

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

    //模闆方法模式,子類繼承DefaultDocumentLoader後可以替換DocumentBuilderFactory和DocumentBuilder的實作
    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);
}
           

這裡使用了工廠方法模式

spring源碼分析(二)配置檔案的解析

解析xml 并 注冊BeanDefinition

有了document之後,就可以很友善的通過document的API來讀取xml的元素。

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    int countBefore = getRegistry().getBeanDefinitionCount();
    //beanDefininition的注冊,委托給了BeanDefnitionDocumentReader
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}
           
spring源碼分析(二)配置檔案的解析

具體的注冊調用過程,就不跟了,總之,最後會調用到org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions方法

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

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

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

到了這一步,spring開始配置檔案解析,具體如何解析,以及如何将配置檔案封裝為BeanDefinitions并進行注冊,下一篇部落格會分析。