1. 程式人生 > >Spring Framework框架解析(1)- 從圖書館示例來看xml檔案的載入過程

Spring Framework框架解析(1)- 從圖書館示例來看xml檔案的載入過程

引言

這個系列是我閱讀Spring原始碼後的一個總結,會從Spring Framework框架的整體結構進行分析,不會先入為主的講解IOC或者AOP的原理,如果讀者有使用Spring的經驗再好不過。鑑於每個人對原始碼閱讀角度的不同,如果文中存在理解有誤的地方希望讀者能夠及時提出,共同進步。文章所分析的原始碼基於5.0.8版本,但使用老版本理解起來問題也不大,因為在框架整體架構上變化並不多。

如果你使用Spring的時間足夠長,相信在最初的開發過程中你一定使用過xml檔案來載入各中bean。雖然現在基本都會通過配置檔案類或者註解來進行載入,但使用xml也有它的優點,這種方式對程式碼的侵入性最小,而且配置第三方bean也比較方便。這篇文章通過一個圖書館的例子來講解xml最原始的載入過程,將載入過程中涉及到的各個模組比做圖書館的各個元素,希望能加深你對Spring框架的理解。

圖書館和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。可以將這一過程想象成現在的圖書還是一堆木頭,而將這些木頭攪拌成木漿只是為了後面更方便的獲取製作圖書的原料而已。

從原始碼角度來說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來操作。

DefaultListableBeanFactory首先是BeanFactory介面的一個實現,BeanFactory定義了通過名稱和型別獲取Bean的一系列方法。

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就相當於圖書館本身了。

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的方法。

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的工作了。這一過程可以通過以下原始碼來分析:

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原始碼深度解析》