Spring IOC實現原理筆記(二) -- 加載XML配置

這篇文章接着上篇Spring IOC實現原理筆記(一)的測試代碼,從ClassPathXmlApplicationContext開始分析spring的裝載對象到容器的實現。

先放出繼承圖,對源碼跟蹤有幫助

ClassPathXmlApplicationContext

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
    this(new String[]{configLocation}, true, (ApplicationContext)null);
}

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
    super(parent);//設置父ApplicationContext
    this.setConfigLocations(configLocations);//設置配置文件路徑
    if(refresh) {
        this.refresh();
    }
}

有點意外,邏輯這麼簡單,然後跟蹤refresh方法進去,跳到了AbstractApplicationContext類,然後就頭大了,如下

public void refresh() throws BeansException, IllegalStateException {
    Object var1 = this.startupShutdownMonitor;
    synchronized(this.startupShutdownMonitor) {
        this.prepareRefresh();//步驟1:準備刷新上下文環境
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();//步驟2:初始化BeanFactory,並進行XML文件讀取
        this.prepareBeanFactory(beanFactory);//步驟3:對BeanFactory各種功能填充
        try {
            this.postProcessBeanFactory(beanFactory);//步驟4:子類富態方法做額外的處理
            this.invokeBeanFactoryPostProcessors(beanFactory);//步驟5:激活各種BeanFactory處理器
            this.registerBeanPostProcessors(beanFactory);//步驟6:註冊攔截Bean創建的Bean處理器
            this.initMessageSource();//步驟7:爲上下文初始化Message源,即國際化處理
            this.initApplicationEventMulticaster();//步驟8:初始化應用消息廣播器
            this.onRefresh();//步驟9:留給子類具體實現
            this.registerListeners();//步驟10:註冊消息廣播器
            this.finishBeanFactoryInitialization(beanFactory);//步驟11:初始化剩餘的單實例(非惰性)
            this.finishRefresh();//步驟12:完成刷新過程,通知生命週期處理器,發出刷新事件
        } catch (BeansException var9) {
            this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt", var9);
            this.destroyBeans();//步驟14
            this.cancelRefresh(var9);//步驟15
            throw var9;
        } finally {
            this.resetCommonCaches();//步驟13:清理緩存
        }

    }
}

因爲本篇只寫關於加載XML配置信息,而上面的【步驟2】就做了這一步工作,跟蹤進去

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    this.refreshBeanFactory();//這裏開始初始化BeanFactory並解析XML信息
    ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
    return beanFactory;
}

protected final void refreshBeanFactory() throws BeansException {
    if(this.hasBeanFactory()) {//銷燬原有的BeanFactory
        this.destroyBeans();
        this.closeBeanFactory();
    }
    try {
        DefaultListableBeanFactory beanFactory = this.createBeanFactory();
        beanFactory.setSerializationId(this.getId());
        this.customizeBeanFactory(beanFactory);
        this.loadBeanDefinitions(beanFactory);//進入解析XML階段
        Object var2 = this.beanFactoryMonitor;
        synchronized(this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    } catch (IOException var5) {
        ...
    }
}

可以看到refreshBeanFactory方法會把當前的BeanFactory銷燬,然後初始化一個DefaultListableBeanFactory實例(作爲AbstractRefreshableApplicationContext的一個成員變量)。

DefaultListableBeanFactory繼承圖如下

DefaultListableBeanFactory

接下來跟蹤解析XML階段:loadBeanDefinitions(beanFactory);

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
    XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
    beanDefinitionReader.setEnvironment(this.getEnvironment());
    beanDefinitionReader.setResourceLoader(this);
    beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));//爲解決聯網獲取xsd,dtd文件的問題
    this.initBeanDefinitionReader(beanDefinitionReader);
    this.loadBeanDefinitions(beanDefinitionReader);
}

上面看到創建了一個XmlBeanDefinitionReader實例,並持有beanFactory的引用(構造方法傳入的)。XmlBeanDefinitionReader就是用於解析XML,並把解析的bean封裝成BeanDefinitionHolder,然後傳給BeanDefinitionRegistry(實際就是DefaultListableBeanFactory),進行相關的緩存。

下面出場的是XML解析的主力XmlBeanDefinitionReader

XmlBeanDefinitionReader

    ...上面的loadBeanDefinitions方法,經過山路十八彎,最終進入下面的方法

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
   try {
//解析XML成一個Document(包:org.w3c.dom)對象
       Document doc = this.doLoadDocument(inputSource, resource);
//解析內容封裝成BeanDefinitionHolder,然後放入BeanDefinitionRegistry中緩存起來
       return this.registerBeanDefinitions(doc, resource);
   } catch...
}

最終解析會進入下面的方法

protected void doRegisterBeanDefinitions(Element root) {
    BeanDefinitionParserDelegate parent = this.delegate;
    this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
//先判斷profile屬性進行判斷是否符合,不符合就退出不用解析了
    if(this.delegate.isDefaultNamespace(root)) {
        String profileSpec = root.getAttribute("profile");
        if(StringUtils.hasText(profileSpec)) {
            String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
            if(!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                return;
            }
        }
    }
...
    this.parseBeanDefinitions(root, this.delegate);//開始解析
    ...
}

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)) {
                    this.parseDefaultElement(ele, delegate);
                } else {
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        delegate.parseCustomElement(root);
    }
}

對XML解析分兩種情況,第一是對默認節點的解析(如bean,alias,import,beans等),第二是對自定義節點的解析(如tx,p,aop等)。解析得到的bean的相關配置信息封裝成BeanDefinitionHolder,然後傳給BeanDefinitionRegistry(實際就是DefaultListableBeanFactory,可從繼承圖看到出),進行相關緩存操作。

如果是解析自定義節點則是需要有相對應的NamespaceHandler,後續分析AOP,會分析NamespaceHandler

//解析默認節點
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
    if(delegate.nodeNameEquals(ele, "import")) {
        this.importBeanDefinitionResource(ele);
    } else if(delegate.nodeNameEquals(ele, "alias")) {
        this.processAliasRegistration(ele);
    } else if(delegate.nodeNameEquals(ele, "bean")) {
        this.processBeanDefinition(ele, delegate);
    } else if(delegate.nodeNameEquals(ele, "beans")) {
        this.doRegisterBeanDefinitions(ele);
    }

}

//解析自定義節點
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    String namespaceUri = this.getNamespaceURI(ele);
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if(handler == null) {
        ...
        return null;
    } else {
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }
}

看一下BeanDefinitionHolder類

public class BeanDefinitionHolder implements BeanMetadataElement {
    private final BeanDefinition beanDefinition;
    private final String beanName;
    private final String[] aliases;
    ...
}

它記錄了一個bean的配置信息,還有bean的名稱、別名,通過它可以把一個bean的配置信息,別名緩存到BeanDefinitionRegistry

總結:

XML解析主要用了SAX技術解析,然後把解析的bean的信息封裝成BeanDefinition存入BeanDefinitionRegistry中,方便之後從容器中獲取實例時根據解析好的信息進行實例化。當然由於Spring提供了多種強大的配置,其中解析的過程並不輕鬆,要經過很多邏輯的處理。

Spring IOC實現原理筆記(一)中有一個BeanFactoryPostProcessor實例,它在哪裏被調用的呢?其實就在【步驟5】

this.invokeBeanFactoryPostProcessors(beanFactory);

參考:

Spring源碼(4.2.2.RELEASE)

《Spring源碼深度解析》

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