Spring Framework框架解析(1)- 從圖書館示例來看xml文件的加載過程

引言

這個系列是我閱讀Spring源碼後的一個總結,會從Spring Framework框架的總體結構進行分析,不會先入爲主的講解IOC或者AOP的原理,若是讀者有使用Spring的經驗再好不過。鑑於每一個人對源碼閱讀角度的不一樣,若是文中存在理解有誤的地方但願讀者可以及時提出,共同進步。文章所分析的源碼基於5.0.8版本,但使用老版本理解起來問題也不大,由於在框架總體架構上變化並很少。java

若是你使用Spring的時間足夠長,相信在最初的開發過程當中你必定使用過xml文件來加載各中bean。雖然如今基本都會經過配置文件類或者註解來進行加載,但使用xml也有它的優勢,這種方式對代碼的侵入性最小,並且配置第三方bean也比較方便。這篇文章經過一個圖書館的例子來說解xml最原始的加載過程,將加載過程當中涉及到的各個模塊比作圖書館的各個元素,但願能加深你對Spring框架的理解。node

圖書館和Spring有許多類似的地方,將圖書館比作bean工廠,從圖書館借書至關於getBean的過程,將圖書館買的書放入圖書館的過程能夠類比註冊bean(registerBeanDefinition)的過程,而生產圖書的過程又能夠類比實例化BeanDefinition的過程,是否是很類似?這裏我會使用下面一段比較原始的代碼來分步講解這一過程。架構

ClassPathResource resource = new ClassPathResource("applicationContext.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); 
reader.loadBeanDefinitions(resource);
Object beanObject = factory.getBean("beanName");

ClassPathResource與Resource

ClassPathResource resource = new ClassPathResource("applicationContext.xml");

這一行代碼比較簡單,它經過一個xml文件初始化了一個Resource,至關於對xml文件作了一個包裝,方便之後將xml文件轉換爲BeanDefinition。能夠將這一過程想象成如今的圖書仍是一堆木頭,而將這些木頭攪拌成木漿只是爲了後面更方便的獲取製做圖書的原料而已。app

從源碼角度來講ClassPathResource繼承自Resource接口,是Spring中對資源的抽象,全部須要使用的資源在Spring中都被抽象爲Resource,它提供了一系列操做資源的方法,好比獲取資源的名稱,資源是否存在等等。Resource接口又繼承了InputStreamSource接口,在InputStreamSource中,提供了一個核心方法,這個方法將資源轉換成InputStream方便後期操做。框架

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

public interface Resource extends InputStreamSource {
    boolean exists();

    URL getURL() throws IOException;

    String getFilename();

    ......
}

BeanFactory、BeanDefinition與DefaultListableBeanFactory

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

DefaultListableBeanFactory就比較重要了,它是一個Bean工廠,至關於圖書館,全部的書都在DefaultListableBeanFactory中,而借書,買書的過程都須要經過DefaultListableBeanFactory來操做。post

DefaultListableBeanFactory首先是BeanFactory接口的一個實現,BeanFactory定義了經過名稱和類型獲取Bean的一系列方法。ui

public interface BeanFactory {
    Object getBean(String name) throws BeansException;

    <T> T getBean(Class<T> requiredType) throws BeansException;

    boolean containsBean(String name);

    ......
}

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable{}

其次從DefaultListableBeanFactory的定義還能夠看到,它在繼承BeanFactory接口的基礎上,還實現了BeanDefinitionRegistry接口。BeanDefinitionRegistry的核心功能是對Bean的註冊,註冊是幹嗎呢?經過圖書館來對比,BeanFactory的getBean至關於從圖書館借書,那麼這些書是哪來的呢?就是經過BeanDefinitionRegistry的registerBeanDefinition方法,它至關於把書放入圖書館,而DefaultListableBeanFactory就至關於圖書館自己了。this

public interface BeanDefinitionRegistry extends AliasRegistry {
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);

    void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

    ......
}

在BeanDefinitionRegistry的定義中還有涉及到一個關鍵接口:BeanDefinition,上面說BeanDefinitionRegistry至關於把書放入圖書館,那麼具體圖書在圖書館中怎麼表示呢?這就是BeanDefinition。BeanDefinition是Bean在Spring中的抽象,也就是說每個Bean在Spring中都對應一個BeanDefinition,它提供了與Bean相對應的屬性,並提供了操做Bean的方法。spa

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
    void setBeanClassName(@Nullable String beanClassName);

    String getBeanClassName();

    void setScope(@Nullable String scope);

    String getScope();

    ......
}

BeanDefinitionReader、BeanDefinitionDocumentReader與XmlBeanDefinitionReader

XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory); 
reader.loadBeanDefinitions(resource);

仍是經過圖書館來類比:圖書館是(DefaultListableBeanFactory),把書(BeanDefinition)放入圖書館的能力對應(BeanDefinitionRegistry),從圖書館拿編號後的書的能力對應(BeanFactory),書的原材料對應(ClassPathResource),如今就缺把書的原材料(ClassPathResource)變成一本本書(BeanDefinition),並將它放入圖書館中了。那麼誰來將原材料(ClassPathResource)變成書(BeanDefinition)並放入到圖書館(DefaultListableBeanFactory)中呢?這就是XmlBeanDefinitionReader的工做了。這一過程能夠經過如下源碼來分析:debug

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader{}
public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader{}

public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
    super(registry);
}

protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
    ......
    this.registry = registry;
    ......
}

public interface BeanDefinitionReader {
    BeanDefinitionRegistry getRegistry();

    ResourceLoader getResourceLoader();

    int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
    ......
}

首先XmlBeanDefinitionReader實現了BeanDefinitionReader接口,BeanDefinitionReader定義了一個關鍵方法loadBeanDefinitions(Resource resource),這個方法將resource裝載到BeanDefinitionRegistry中,BeanDefinitionRegistry經過XmlBeanDefinitionReader的構造方法傳入。具體loadBeanDefinitions又是怎麼作的呢?再來繼續查看源代碼:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    ......
        //經過InputStreamSource接口的定義的getInputStream方法獲取InputStream
        InputStream inputStream = encodedResource.getResource().getInputStream();
        try {
            //將InputStream包裝成InputSource
            InputSource inputSource = new InputSource(inputStream);
            if (encodedResource.getEncoding() != null) {
                inputSource.setEncoding(encodedResource.getEncoding());
            }
            //將Source裝載到BeanDefinitionRegistry中
            return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
        }
        finally {
            inputStream.close();
        }
    ......
}

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
        throws BeanDefinitionStoreException {
    ......
    try {
        //將Source包裝成Document
        Document doc = doLoadDocument(inputSource, resource);
        //將Document裝載到BeanDefinitionRegistry中
        return registerBeanDefinitions(doc, resource);
    }
    ......
}

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    //這裏建立了一個DefaultBeanDefinitionDocumentReader
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    int countBefore = getRegistry().getBeanDefinitionCount();

    //調用DefaultBeanDefinitionDocumentReader的registerBeanDefinitions方法將將Document裝載到BeanDefinitionRegistry中
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

首先將Resource轉換爲EncodedResource,而後經過getInputStream獲取InputStream,調用doLoadBeanDefinitions方法來裝載資源,在doLoadBeanDefinitions方法中,首先將Resource包裝成Document方便操做元素節點,而後把解析並裝載Document的功能委託了給BeanDefinitionDocumentReader,這裏使用了一個默認的DefaultBeanDefinitionDocumentReader實現。那麼能夠想象DefaultBeanDefinitionDocumentReader中作了兩件事:將Document解析爲BeanDefinitions,而後將BeanDefinitions裝載到BeanDefinitionRegistry中。

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
    this.readerContext = readerContext;
    logger.debug("Loading bean definitions");
    //從Document中獲取到Element
    Element root = doc.getDocumentElement();

    //具體的解析過程
    doRegisterBeanDefinitions(root);
}

protected void doRegisterBeanDefinitions(Element root) {
    ......
    //前置解析,默認爲空,能夠重寫
    preProcessXml(root);
    //具體的解析xml並注入到過程
    parseBeanDefinitions(root, this.delegate);
    //後置解析,默認爲空,能夠重寫
    postProcessXml(root);
    ......
}

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    ......
    parseDefaultElement(ele, delegate);
    ......
}

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

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
    //將元素包裝成BeanDefinitionHolder,方便操做BeanDefinition
    BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
    if (bdHolder != null) {
        bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
        try {
            //具體的注入方法
            BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
        }
        ......
    }
}

public static void registerBeanDefinition(
        BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
        throws BeanDefinitionStoreException {
    ......
    //經過BeanDefinitionRegistry將元素注入到DefaultListableBeanFactory中
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    ......
}

跟以前猜想的同樣,首先經過parseBeanDefinitions方法將全部的xml節點分步解析,以後將解析後的節點包裝成BeanDefinitionHolder對象,最後經過BeanDefinitionRegistry的registerBeanDefinition方法將元素注入到BeanDefinitionRegistry中。

整個解析到注入過程很是複雜,我只列出了核心步驟,從中能夠看到XmlBeanDefinitionReader是怎麼一步步將xml中的Bean節點變爲BeanDefinition並放入到DefaultListableBeanFactory中的。仍是用圖書館來類比:首先將原材料(ClassPathResource)變成紙張(Document),而後將紙張(Document)經過書籍製造工廠(BeanDefinitionDocumentReader)組裝成一本本書籍(BeanDefinition),而後書籍製造工廠(BeanDefinitionDocumentReader)將一本本書籍(BeanDefinition)送到圖書館(DefaultListableBeanFactory),而XmlBeanDefinitionReader就扮演了這一整個過程的組合功能。

總結

至此,整個圖書館功能就齊全了,原材料能夠造書,書能夠放入圖書館,而且你也能夠很方便的從圖書館借書。能夠說Spring設計理念也在這一過程當中獲得體現,它將Bean的解析,Bean的定義,Bean的生產以及Bean的獲取每一步都單獨抽離開來,互不干擾,最後經過DefaultListableBeanFactory將它們整合到一塊兒供用戶使用。怎麼說呢,這一過程回過頭來並無什麼神奇的地方,但能清晰的將每一個功能都抽象出來自己就須要很是好的抽象設計能力,而對這一過程的反覆閱讀與分析,必定能讓你在設計抽象能力上有必定的提高。

看完後你以爲這一過程類比是否恰當呢?若是你有更貼近生活的例子,不妨留言一塊兒探討,共同進步。說完DefaultListableBeanFactory,在下一篇文章中將會講講ApplicationContext接口,並對它的部分實現類作一個簡單分析。

參考資料:

  • 《Spring揭祕》
  • 《Spring源碼深度解析》
相關文章
相關標籤/搜索