1.2.Spring源碼解析——容器的基礎(chǔ)XmlBeanFactory

1.通過以下代碼分析

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("Test.xml"));

通過XmlBeanFactory初始化時(shí)序圖,看看上面代碼的執(zhí)行邏輯


容器的基礎(chǔ).png

?在測(cè)試的BeanFactoryTest中首先調(diào)用ClassPathResource的構(gòu)造函數(shù)來構(gòu)造Resource資源文件的實(shí)例對(duì)象,后續(xù)的資源處理可以用Resource提供的各種服務(wù)操作,有了Resource之后就可以進(jìn)行XmlBeanFactory的初始化了。


2.配置文件的封裝

?在Java中,將不同來源的資源抽象成URL,通過注冊(cè)不同的handle(URLStreamHandler)來處理不同來源的資源的讀取邏輯,一般handle的類型使用不同的前綴(協(xié)議,Protocol)來識(shí)別,如“file”,“http”等,然而URL沒有默認(rèn)定義相會(huì)Classpath或者ServletContext等資源的handle,雖然可以注冊(cè)自己的Handler來解析但是需要了解URL的實(shí)現(xiàn)機(jī)制,而且URL也沒有提供一些基本的方法,如檢查當(dāng)前資源是不是存在,可讀的方法。
Spring對(duì)其內(nèi)部使用到的資源進(jìn)行了自己的抽象結(jié)構(gòu):Resource接口來封裝底層資源。

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

public interface Resource extends InputStreamSource {
  //是否存在
  boolean exists();
  //是否可讀
  boolean isReadable();
  //是否處于打開的狀態(tài)
  boolean isOpen();
  URL getURL() throws IOException;
  URI getURI() throws IOException;
  File getFile() throws IOException;
  long contentLength() throws IOException;
  long lastModified() throws IOException;
  //根據(jù)當(dāng)前資源創(chuàng)建一個(gè)相對(duì)資源
  Resource createRelative(String relativePath) throws IOException;'
  String getFilename();
  //在錯(cuò)誤處理中的打印信息
  String getDescription();
}

?InputStreamSource封裝任何能返回InputStream的類,比如File,Classpath下的資源和Byte Array等。
?Resource接口抽象了所有Spring內(nèi)部使用到的底層資源:File,URL,ClassPath等。首先,定義了3個(gè)用于判斷當(dāng)前資源狀態(tài)的方法。另外還提供了不同資源到URL,URI,F(xiàn)ile類型的轉(zhuǎn)換,以及獲取lastModified屬性,文件名的方法。還提供了基于當(dāng)前資源創(chuàng)建一個(gè)相對(duì)資源的方法;createRelative()。
?對(duì)于不同來源的資源文件都有相應(yīng)的Resource實(shí)現(xiàn):羅列部分。


容器的基礎(chǔ)2.png

?對(duì)于實(shí)現(xiàn)的方式很簡(jiǎn)單,以getInputStream為例,ClassPathResource中實(shí)現(xiàn)的方式是通過class或者classLoader提供的底層方法進(jìn)行調(diào)用,而對(duì)于FileSystemResource的實(shí)現(xiàn)直接使用FileInputStream對(duì)文件進(jìn)行實(shí)例化。

//ClassPathResource.java中的getInputStream
public InputStream getInputStream() throws IOException {
   InputStream is;
   if (this.clazz != null) {
      is = this.clazz.getResourceAsStream(this.path);
   }
   else if (this.classLoader != null) {
      is = this.classLoader.getResourceAsStream(this.path);
   }
   else {
      is = ClassLoader.getSystemResourceAsStream(this.path);
   }
   if (is == null) {
      throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
   }
   return is;
}

FileSystemResource.java 中的getInputStream
public InputStream getInputStream() throws IOException {
   return new FileInputStream(this.file);
}

通過Resource相關(guān)類完成了對(duì)配置文件進(jìn)行了封裝后配置文件的讀取工作就全權(quán)交給XmlBeanDefinitionReader處理了。


3.XmlBeanFactory的初始化過程:

XmlBeanFactory的初始化有若干辦法,Spring提供了很多的構(gòu)造函數(shù),代碼如下:

public class XmlBeanFactory extends DefaultListableBeanFactory {
//獲取XmlBeanDefinitionReader用于加載resource資源用
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

public XmlBeanFactory(Resource resource) throws BeansException {
             //調(diào)用XmlBeanFactory(Resource,BeanFactory)構(gòu)造方法
                  this(resource, null);
}

//parentBeanFactory為父類BeanFactory用于factory合并,可以為空
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
         super(parentBeanFactory);
        //這里才是加載資源的正真實(shí)現(xiàn)
         this.reader.loadBeanDefinitions(resource);
}
}

?在調(diào)用加載資源文件之前還有一個(gè)調(diào)用父類構(gòu)造器初始化的過程:super(parentBeanFactory),跟蹤代碼到父類AbstractAutowireCapableBeanFactory構(gòu)造器

public AbstractAutowireCapableBeanFactory() {
   super();
   ignoreDependencyInterface(BeanNameAware.class);
   ignoreDependencyInterface(BeanFactoryAware.class);
   ignoreDependencyInterface(BeanClassLoaderAware.class);
}

?其中ignoreDependencyInterface方法的主要功能是忽略給定接口的自動(dòng)裝備功能,這樣做的目的是:
?如果A中有屬性B,那么當(dāng)Spring在獲取A的Bean的時(shí)候如果其屬性B還沒有初始化,那么Spring就會(huì)自動(dòng)初始化B,這也是Spring中提供的一個(gè)重要特性。
?但是,某些情況下,B不會(huì)被初始化,其中的一種情況就是B實(shí)現(xiàn)了BeanNameAware接口。Spring中是這樣介紹的:自動(dòng)裝配時(shí)忽略給定的依賴接口,典型應(yīng)用就是通過其他方式解析Application上下文注冊(cè)依賴,類似于BeanFactory通過BeanNameAware進(jìn)行注冊(cè)或者ApplicationContext通過ApplicationContextAware進(jìn)行注入。


4.加載Bean

容器的基礎(chǔ)3.png

從 this.reader.loadBeanDefinitions(resource)這行代碼作為切入點(diǎn)。
(1)封裝資源文件:當(dāng)進(jìn)入XmlBeanDefinitionReader后首先對(duì)參數(shù)Resource使用EncodedResource類進(jìn)行封裝
(2)獲取輸入流。從Resource中獲取對(duì)應(yīng)的InputStream并構(gòu)造InputSource
(3)通過構(gòu)造的InputSource實(shí)例和Resource實(shí)例繼續(xù)調(diào)用函數(shù)doLoadBeanDefinition。
具體的實(shí)現(xiàn)過程:

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

EncodedResource這個(gè)類樹妖適用于對(duì)資源文件的編碼進(jìn)行處理。其中主要邏輯體現(xiàn)在類中的getReader()方法中,當(dāng)設(shè)置了編碼屬性的時(shí)候Spring會(huì)使用相應(yīng)的編碼作為輸入流的編碼

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

上面代碼構(gòu)造了一個(gè)有編碼的inputStreamReader。當(dāng)構(gòu)造好encodedResource對(duì)象后,再次轉(zhuǎn)入了可服用方法LoadBeanDefinition(new EncodedResource(resource))。
這個(gè)方法內(nèi)部才是正真的數(shù)據(jù)準(zhǔn)備階段;

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());
        }
        //通過屬性來記錄已經(jīng)加載過的資源
        Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet<EncodedResource>(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected cyclic loading of " + encodedResource + " - check your import definitions!");
        }
        try {
            //從encodedResource中獲取已經(jīng)編碼封裝的Resource對(duì)象并再次從Resource中獲取inputStream
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                //InputSource這個(gè)類并不是來自Spring,他的全路徑時(shí)prg.xml.sax.inputStream
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                //進(jìn)入邏輯核心部分
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                //關(guān)閉輸入流
                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();
            }
        }
    }

再次整理一次,首先對(duì)傳入的resource參數(shù)做封裝,目的是考慮到Resource可能存在編碼要求的情況,其次,通過SAX讀取XML文件的方式來準(zhǔn)備InputSource對(duì)象,最后將準(zhǔn)備的數(shù)據(jù)通過參數(shù)傳入正真的核心處理部分doLoadBeanDefinitions(inputSource, encodedResource.getResource());

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {
        try {
            //獲取對(duì)XML文件的驗(yàn)證模式
            int validationMode = getValidationModeForResource(resource);
            //加載XML文件,并得到對(duì)應(yīng)的Document
            Document doc = this.documentLoader.loadDocument(
                    inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
            //根絕返回的Document注冊(cè)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);
        }
    }

在這個(gè)方法里面主要做了三件事;
(1)獲取對(duì)XML文件的驗(yàn)證模式
(2)加載XML文件,并的到對(duì)應(yīng)的Document
(3)根據(jù)返回的Document注冊(cè)Bean信息

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,545評(píng)論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,268評(píng)論 6 342
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,939評(píng)論 1 92
  • 馬上進(jìn)入四月,由于最近天氣變化很是劇烈,早上起來還是感覺很冷,拉開窗簾看到樓下已經(jīng)盛開的桃花心情也會(huì)變得很美麗。
    每天都好困閱讀 151評(píng)論 0 0
  • 旅行已接近尾聲。 每年假期都會(huì)帶你出去旅行,一是你辛苦學(xué)習(xí)一學(xué)年了,出去放松心情;二是外出旅行可以增進(jìn)親子感情;最...
    素面迎風(fēng)閱讀 386評(píng)論 1 0

友情鏈接更多精彩內(nèi)容