1. 程式人生 > >Spring原始碼深度解析總結(3)—— 配置檔案的讀取和Bean的載入(一)

Spring原始碼深度解析總結(3)—— 配置檔案的讀取和Bean的載入(一)

上一篇總結了Spring利用Resource介面對配置檔案的封裝,接下來我們要看看Spring對封裝好的資源是如何解析的,以及如何利用解析出的資訊載入Bean的。

BeanFactory bf = new XmlBeanFactory(new ClassPathResoure("test.xml"));
我們繼續來看這個例子,上一篇中ClassPathResource已經將配置檔案封裝成了Resource物件,現在Spring就可以對XmlBeanFactory進行初始化過程了,XmlBeanFactory有若干個初始化方法,我們這裡使用的是針對Resource例項進行初始化的方法
package org.springframework.beans.factory.xml;
public class XmlBeanFactory extends DefaultListableBeanFactory {
    public XmlBeanFactory(Resource resource) throws BeansException {
	    this(resource, null);
    }
    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
	    super(parentBeanFactory);
	    this.reader.loadBeanDefinitions(resource);
    }
}

上面函式中的程式碼this.reader.loadBeanDefinitions(resource)才是資源載入的真正實現,也是下面要了解的重點,在此之前,在這個方法上面有一個super(parentBeanFactory)方法,我們跟蹤到父類的建構函式中看一看

package org.springframework.beans.factory.support;
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
    public AbstractAutowireCapableBeanFactory() {
        super();
        ignoreDependencyInterface(BeanNameAware.class);
        ignoreDependencyInterface(BeanFactoryAware.class);
        ignoreDependencyInterface(BeanClassLoaderAware.class);
    }
}

這裡有必要提及一下ignoreDependencyInterface方法。這個介面的主要功能是忽略給定介面的自動裝配功能,那麼這麼做的目的是什麼呢?

我們知道在Spring中普通的Bean是無意識的,也就是說這些Bean是意識不到Spring這個容器的存在的,這樣的優點是這些Bean是和Spring解耦的,但是缺點也是顯而易見的,那就是我們在這些Bean中獲取到關於容器的資訊。而實現了*Aware介面的Bean就可以解決這個問題,但是這樣就會使程式與Spring耦合,如果離開Spring框架就會無法執行。如果我們和普通Bean一樣用new方法來建立的話是無法得到Spring的資訊的,我們需要把Bean交給Spring容器進行管理,讓Spring把想要的資訊裝載到Bean中。這也就意味著實現了*Aware介面的Bean是不能和普通Bean一起自動裝載的,所以需要忽略它們,至於為什麼只是忽略了實現了BeanNameAware、BeanFactoryAware、BeanClassLoaderAware這三個介面的類,我們可以看一下AbstractAutowireCapableBeanFactory

中的定義方法就知道了

    protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
        if (System.getSecurityManager() != null) {
            AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                invokeAwareMethods(beanName, bean);
                return null;
            }, getAccessControlContext());
        }
	else {
            invokeAwareMethods(beanName, bean);
	}

	Object wrappedBean = bean;
	if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
	}

	try {
            invokeInitMethods(beanName, wrappedBean, mbd);
	}
	catch (Throwable ex) {
            throw new BeanCreationException(
                (mbd != null ? mbd.getResourceDescription() : null),
                    beanName, "Invocation of init method failed", ex);
	}
	if (mbd == null || !mbd.isSynthetic()) {
		wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
	}
	return wrappedBean;
    }
    private void invokeAwareMethods(final String beanName, final Object bean) {
	if (bean instanceof Aware) {
            if (bean instanceof BeanNameAware) {
                ((BeanNameAware) bean).setBeanName(beanName);
            }
            if (bean instanceof BeanClassLoaderAware) {
                ClassLoader bcl = getBeanClassLoader();
                if (bcl != null) {
                    ((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
                }
            }
            if (bean instanceof BeanFactoryAware) {
                ((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
            }
	}
    }

AbstractAutowireCapableBeanFactory的定義方法中我們可以看到他呼叫了自己的invokeAwareMethods方法,這個方法的中處理了實現了BeanNameAware、BeanFactoryAware、BeanClassLoaderAware這三個介面的類,所以前面才只是忽略了這三個介面,至於其他幾個Aware型別的介面為什麼沒有忽略,在後面看ApplicationContext的時候我們會遇到。

好了,講完ignoreDependencyInterface方法後我們就要開始講整個XmlBeanFactory的初始化方法中的重頭戲——loadBeanDefinitions(resource),在下面我們先總結一下這個方法的流程,以便可以有目的性的看原始碼:

(1)封裝資原始檔。當進入XmlBeanDefinitionReader後首先對引數Resource使用EncodeResource類進行封裝。

(2)獲取輸入流:從Resource中獲取對應的InputStream並構造InputSource

(3)通過構造的InputSource例項和Resource例項繼續呼叫doLoadBeanDefinitions

首先我們先要搞清楚EncodeResource類的作用是什麼,通過名稱我們可以大致推斷這個類主要是對資原始檔的編碼進行處理的,其主要邏輯體現在getReader()方法中,當設定了編碼屬性的時候Spring會使用相應的編碼作為輸入流的編碼

package org.springframework.core.io.support;
public class EncodedResource implements InputStreamSource {
    public Reader getReader() throws IOException {
	if (this.charset != null) {
	    return new InputStreamReader(this.resource.getInputStream(), this.charset);
	}
	else if (this.encoding != null) {
	    return new InputStreamReader(this.resource.getInputStream(), this.encoding);
	}
	else {
	    return new InputStreamReader(this.resource.getInputStream());
	}
    }
}

當構造好encodeResource物件後,再次傳入可複用方法loadBeanDefinitions(new EncodeResource(resource))中

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

上面的方法才是真正的資料準備階段,也是我們需要研究的重頭戲

    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<>(4);
	    this.resourcesCurrentlyBeingLoaded.set(currentResources);
	}
	if (!currentResources.add(encodedResource)) {
	    throw new BeanDefinitionStoreException(
		"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
	}
	try {
            //從EncodeResource中獲取Resource物件,並從Resource中獲取InputStream
	    InputStream inputStream = encodedResource.getResource().getInputStream();
	    try {
                /將InputStream封裝為InputSource物件
		InputSource inputSource = new InputSource(inputStream);
		if (encodedResource.getEncoding() != null) {
                    //如果encodedResource中對編碼格式有要求,則在inputSource中設定編碼屬性。
                    //注意:InputSource類並不屬於Spring框架,全路徑是org.xml.sax.InputSource
		    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();
		}
	    }
	}

核心部分程式碼

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
        try {
            //將封裝的inputSource載入成Document
            Document doc = doLoadDocument(inputSource, resource);
            //通過解析Document定義和註冊Bean
            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 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());
    }

這個方法裡一共有兩個重要的函式一個是documentLoader.loadDocument另一個是getValidationModeForResource,由於第二個函式的返回值是第一個函式的一個引數,所以我們先從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;
    }

從上面的程式碼我們可以推斷出這個函式的作用是獲取XML的驗證模式,那麼XML的驗證模式是什麼呢,簡單的說XML有兩種驗證模式:DTD和XSD,DTD是Document Type Definition的縮寫,即文件型別定義;XSD是XML Schemas Definition的縮寫即XML Schemas定義,詳細的區別這裡不再贅述,我們只需要關注他們兩個的區別就可以了。DTD是有<!DOCTYPE ...>宣告的,而XSD是有<...xsi:schemeLocation="...">宣告的,我們知道了兩者的區別後就很容易判斷資源的驗證模式了。我們繼續跟蹤delectValidationMode(resource)方法中呼叫的delectValidationMode(intputStream)方法

    public int detectValidationMode(InputStream inputStream) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        try {
            boolean isDtdValidated = false;
            String content;
            while ((content = reader.readLine()) != null) {
                content = consumeCommentTokens(content);
                if (this.inComment || !StringUtils.hasText(content)) {
                    continue;
                }
                //判斷讀取的行中有DOCTYPE,則一定是DTD模式
                if (hasDoctype(content)) {
                    isDtdValidated = true;
                    break;
                }
                //DTD模式一定在'<'符號前出現,如果當前行中出現了'<'則表明一定是XSD模式
                if (hasOpeningTag(content)) {
                    break;
                }
            }
            return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
        }
        catch (CharConversionException ex) {
            return VALIDATION_AUTO;
        }
        finally {
            reader.close();
        }
    }

取得驗證模式後就可以進行檔案的載入了,下面是檔案載入的程式碼

package org.springframework.beans.factory.xml;
public class DefaultDocumentLoader implements DocumentLoader {
    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);
    }
}

需要注意的是這裡引用的是DefaultDocumentLoader中的方法,因為XmlBeanDefinition中只是Document介面,整個方法和SAX解析XML沒什麼區別,都是建立檔案Builder的工廠然後通過工廠建立builder再呼叫builder的解析方法完成解析。至此,Document的解析就完成了,下面就要開始對Bean進行載入和註冊了。讓我們回過頭來看XmlBeanDefinition中的第二個重要的方法——registerBeanDefinitions(doc, resource)。

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        //使用DefaultBeanDefinitionDocumentReader 例項化 BeanDefinitionDocumentReader
        BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
        //記錄統計前BeanDefinition的載入個數
        int countBefore = getRegistry().getBeanDefinitionCount();
        //載入及註冊BeanDefinition
        documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
        //返回本次註冊BeanDefinition的個數
        return getRegistry().getBeanDefinitionCount() - countBefore;
    }

顯然這個函式依然沒有涉及到核心的邏輯,我們需要繼續往下跟蹤,而且跟蹤的函式也很明顯就是egisterBeanDefinitions(doc, createReaderContext(resource))方法,下面是這個方法的程式碼:

    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) {
	//處理遞迴和巢狀
	BeanDefinitionParserDelegate parent = this.delegate;
	this.delegate = createDelegate(getReaderContext(), root, parent);
	//驗證是否符合Spring規範以及處理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)) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
                            "] not matching: " + getReaderContext().getResource());
		    }
		    return;
                }
            }
        }
	//解析Bean前的處理,預設是空函式,留給子類去實現
	preProcessXml(root);
	//解析Bean,整個函式的重點
	parseBeanDefinitions(root, this.delegate);
	//解析Bean後的處理,預設是空函式,留給子類去實現
	postProcessXml(root);
	//處理遞迴和巢狀
	this.delegate = parent;
    }

我們繼續向下跟蹤,程式碼如下

    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)) {
		        //如果是預設Bean宣告用預設解析方法解析Bean
			parseDefaultElement(ele, delegate);
		    }
		    else {
		        //如果是自定義Bean宣告用自定義解析方法解析Bean
			delegate.parseCustomElement(ele);
		    }
		}
	    }
	}
	else {
	    //如果是自定義Bean宣告用自定義解析方法解析Bean
	    delegate.parseCustomElement(root);
	}
    }

上面的程式碼看起來邏輯還是很容易搞懂的,Spring對於預設宣告和自定義宣告的解析差別還是比較大的,這方面的程式碼到下一篇在繼續總結。