Spring源碼一(容器的基本實現1)

前言

本文基於《Spring源碼深度解析》學習, 《Spring源碼深度解析》講解的Spring版本低於Spring3.1,當前閱讀的版本爲Spring5.x,因此在文章內容上會有所不一樣。
這篇文章基於有必定Spring 基礎的人進行講解,因此有些問題並不作詳細的實現, 若有分析不合理或是錯誤的地方請指教指正,不勝感激。java

1、在非ApplicationContext下使用

在《Spring源碼深度解析》中有這樣一個實例:spring

public class BeanFactoryTest {
    @test
    public void testSimpleLoad() {
        BeanFactory bf = new XmBeanFactory(new ClassPathResource("beanFactoryTest.xml");
        MyTestBean bean = (MyTestBean) bf.getBean("myTestBean");
        assertEquals("testStr", bean.getTestStr());        
    }
}

固然在這裏會有一個Spring的配置文件 beanFactoryTest.xml, 當使用xml文件的時候,會發現文件頭有一些<?xml ……> <beans></beans>
這樣的標籤, 建議學習一下DOM,DOM2, DOM3結構, 以便更加清晰的瞭解xml文件頭中的內容的真正意義。
這裏的配置文件只寫一個相關的bean設計模式

<bean id="myTestBean" class="bean.MyTestBean"></bean>

這段代碼的做用就是如下幾點:網絡

  1. 讀取配置文件。
  2. 在Spring的配置中找到bean,並實例化。
  3. 使用斷言判斷實例的屬性。

@deprecated as of Spring 3.1 in favor of {@link DefaultListableBeanFactory} and {@link XmlBeanDefinitionReader}
這是該類在當前版本的部分註釋,在Spring3.1之後這個類被棄用了,Spring官方不建議使用這個類。建議使用以上這兩個類。XmlBeanDefinitionReader本就是之前定義在這個類中的一個final的實例,而DefaultListableBeanFactory則是該類的超類。加載配置文件能夠這樣使用:app

Resource resource = new ClassPathResource("beanFactoryTest.xml");
 BeanFactory beanFactory = new DefaultListableBeanFactory();
 BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) beanFactory);
 beanDefinitionReader.loadBeanDefinitions(resource);
 MyTestBean bean = (MyTestBean) bf.getBean("myTestBean");
 assertEquals("testStr", bean.getTestStr());

這個過程和上面的過程實際上的實現只用一點不一樣、前者是在建立時就直接實例化了bean, 後者則是在加載的時候才實例化bean:ide

  1. 讀取配置文件。
  2. 建立BeanFactory。
  3. 建立BeanDefinitionReader。
  4. 加載resource資源。
  5. 獲取bean實例(實例化bean)。
  6. 使用斷言判斷實例的屬性。

事實上在實際的使用中,絕大多數時候都會經過如下這種ApplicationContext的方式來加載Spring的配置文件並進行解析, 之後會寫到這裏的實現:函數

ApplicationContext sc = new ClassPathXmlApplicationContext("applicationContext.xml");

2、加載並解析配置文件

Resource resource = new ClassPathResource("beanFactoryTest.xml");

經過ClassPathResource 加載配置文件,並構建該實例的時候,是使用Resource接口進行定義的, 這也就說明了建立的其實是Resource的實例,經過查看Resource 的源碼不難發現,Resource對Java中將要使用的資源進行了抽象,Spring的設計中幾乎全部能夠加載資源的
類須要直接或間接的實現Resource 這個接口。下面能夠看一下這個接口:學習

boolean exists();    // 判斷是否資源是否存在
default boolean isReadable() {  // 判斷資源是否可讀
    return exists();
}
default boolean isOpen() { // 判斷文件是否打開
    return false;
}
default boolean isFile() { // 判斷文件是不是文件系統中的文件,Spring5.0後加入的
    return false;
}
URL getURL() throws IOException; // 獲取文件的URL

URI getURI() throws IOException; // 獲取文件的URI
File getFile() throws IOException; // 獲取文件
default ReadableByteChannel readableChannel() throws IOException {  // 返回一個Channel, 擁有最大效率的讀操做
    return Channels.newChannel(getInputStream());
}
long contentLength() throws IOException; // 返回資源解析後的長度
long lastModified() throws IOException; // 最後一次休干時間
Resource createRelative(String relativePath) throws IOException; // 基於當前資源建立一個相對資源
@Nullable
String getFilename(); // 獲取文件名  for example, "myfile.txt"
String getDescription(); // 獲取資源描述, 當發生錯誤時將被打印

經過查看源碼,還有一點能夠發現, Resource接口繼承了InputStreamSource 接口,下面來看下這個接口:ui

public interface InputStreamSource {

    /**
     * Return an {@link InputStream} for the content of an underlying resource.
     * <p>It is expected that each call creates a <i>fresh</i> stream.
     * <p>This requirement is particularly important when you consider an API such
     * as JavaMail, which needs to be able to read the stream multiple times when
     * creating mail attachments. For such a use case, it is <i>required</i>
     * that each {@code getInputStream()} call returns a fresh stream.
     * @return the input stream for the underlying resource (must not be {@code null})
     * @throws java.io.FileNotFoundException if the underlying resource doesn't exist
     * @throws IOException if the content stream could not be opened
     */
    InputStream getInputStream() throws IOException;

}

這個接口的做用很是簡單而且是頂層接口,它的做用就是返回一個InputStream, 最簡單的做用卻提供了最大的方便, 由於全部資源加載類幾乎都直接或間接的實現了Resource, 這也就意味着, 幾乎全部的資源加載類均可以獲得一個InputStream, 這將使得在資源加載以後能輕易地獲得一個InputStream, 這很是重要。經過InputStream, Spring使全部的資源文件都能進行統一的管理了, 優勢是不言而喻的。至於實現是很是簡單的, ClassPathResource 中的實現方式即是經過class或者classLoader提供的底層方法進行調用的, 對於FileSystemResource的實現其實更加簡單, 就是直接使用FileInputStream對文件進行實例化。this

3、DefaultListableBeanFactory、XmlBeanDefinitionReader和BeanDefinitionRegistry

配置文件加載完成後進行的下一步操做是這樣的,這和3.1以前的版本不太同樣,至於爲何要棄用XmlBeanFactory(我猜是爲了對其餘部分進行設計,從而讓這部分代碼更加充分的進行解耦):

BeanFactory beanFactory = new DefaultListableBeanFactory();
 BeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) beanFactory);
 beanDefinitionReader.loadBeanDefinitions(resource);

配置文件加載完成後,建立了一個BeanFactory的實例。DefaultListableBeanFactory: 見名知意,這是一個默承認列的bean工廠類,註釋用說道,典型的一個應用就是在第一次定義時註冊全部bean。接下來的操做是利用多態將上一步建立的BeanFactory的實例轉成BeanDefinitionRegistry, 由於下一步須要讀取xml文件中定義的內容,這也就是XmlBeanDefinitionReader的做用,而XmlBeanDefinitionReader在實例化的時候須要一個bean的定義註冊機,因此就進行了以上操做, 事實上:在建立BeanFactory實例時,一樣能夠定義爲BeanDefinitionRegistry類型。下面詳細說下一這三個類的做用:

  1. DefaultListableBeanFactory:在定義時經過當前類的類加載器(若是不存在就向上級加載器尋找直到系統加載器)下的全部的bean進行註冊,注意這裏只是進行註冊而已。
  2. BeanDefinitionRegistry: 爲了註冊Spring持有的bean的一個接口,是在BeanFactory,AbstractBeanDefinition中間的一層接口。
  3. XmlBeanDefinitionReader:註釋中這樣寫道
/**
     * Bean definition reader for XML bean definitions.
     * Delegates the actual XML document reading to an implementation
     * of the {@link BeanDefinitionDocumentReader} interface.
     *
     * <p>Typically applied to a
     * {@link org.springframework.beans.factory.support.DefaultListableBeanFactory}
     * or a {@link org.springframework.context.support.GenericApplicationContext}.
     *
     * <p>This class loads a DOM document and applies the BeanDefinitionDocumentReader to it.
     * The document reader will register each bean definition with the given bean factory,
     * talking to the latter's implementation of the
     * {@link org.springframework.beans.factory.support.BeanDefinitionRegistry} interface.
    */
    只說最重要的一個部分, 在這裏它須要委託一個真正的xml文檔讀取器來讀取文檔內容,也就是BeanDefinitionDocumentReader,而這個文檔讀取器將讀取全部的bean註冊內容,而這些資源正是一、2中所獲得的。

接下來就是重要的一步,beanDefinitionReader.loadBeanDefinitions(resource); 在解析了配置文件中的bean後,事實上配置文件中bean並無被真正的加載,而且上面的步驟也只是對全部的bean進行了一次註冊, 因此,這個時候load了resoure中的內容, 在編碼沒有問題之後,而且resource中bean能夠在類加載器下找到這些類,這時就對這些bean進行加載,實例化。下面跟蹤代碼到這個實現中看看Spring 是怎麼作的:

/**
     * Create new XmlBeanDefinitionReader for the given bean factory.
     * @param registry the BeanFactory to load bean definitions into,
     * in the form of a BeanDefinitionRegistry
     */
    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        super(registry);
    }


protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
        this.registry = registry;

        // Determine ResourceLoader to use.
        if (this.registry instanceof ResourceLoader) {
            this.resourceLoader = (ResourceLoader) this.registry;
        }
        else {
            this.resourceLoader = new PathMatchingResourcePatternResolver();
        }

        // Inherit Environment if possible
        if (this.registry instanceof EnvironmentCapable) {
            this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
        }
        else {
            this.environment = new StandardEnvironment();
        }
    }

在實例化XmlBeanDefinitionReader 的過程當中,在構造函數中調用了其超類的構造函數,而在超類中對其所處換環境進行的判斷,所謂的環境呢,事實上指得就是是經過BeanFactory, 仍是經過ApplicationContext加載的上下文,這也就意味着不一樣方式加載可能存在某些不一樣。寫這些的目的實際上是爲了引出這裏的一個咱們十分關注的東西, 就是自動裝配。在AbstractAutowireCapableBeanFactory這個抽象類的構造方法中實現了相關的自動裝配,在BeanDefinitionRegistry 和DefaultListableBeanFactory中都繼承了這個抽象類, 並在其構造函數內直接調用了其超類的構造函數也就是:

/**
     * Create a new AbstractAutowireCapableBeanFactory.
     */
    public AbstractAutowireCapableBeanFactory() {
        super();
        ignoreDependencyInterface(BeanNameAware.class);
        ignoreDependencyInterface(BeanFactoryAware.class);
        ignoreDependencyInterface(BeanClassLoaderAware.class);
    }
這裏有必要說起一下 ignoreDependencyInterface();這個方法。它的主要功能就是忽略接口中的自動裝配, 那麼這樣作的目的是什麼呢?會產生什麼樣的效果呢?舉例來講, 當A中有屬性B, 那麼Spring在獲取A的時候就會去先去獲取B, 然而有些時候Spring不會這樣作,就是Spring經過BeanNameAware、BeanFactoryAware和BeanClassLoaderAware進行注入的, 也就是根據環境的不一樣, Spring會選擇相應的自從裝配的方式。在不是當前環境中的注入,Spring並不會再當前環境對Bean進行自動裝配。相似於,BeanFactory經過BeanFactoryAwar進行注入或者ApplicationContext經過ApplicationContextAware進行注入。

通過了這麼長時間的鋪墊,終於應該進入正題了, 就是進入經過loadBeanDefinitions(resource)方法加載這個文件。這個方法這樣實現:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isTraceEnabled()) {
            logger.trace("Loading XML bean definitions from " + encodedResource);
        }

        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 {
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    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();
            }
        }
    }

這段代碼很容易理解,不過真正實現的核心代碼是在return doLoadBeanDefinitions(inputSource, encodedResource.getResource());這裏實現的。下面是這個方法的核心代碼:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
            throws BeanDefinitionStoreException {

        try {
            Document doc = doLoadDocument(inputSource, resource);
            int count = registerBeanDefinitions(doc, resource);
            if (logger.isDebugEnabled()) {
                logger.debug("Loaded " + count + " bean definitions from " + resource);
            }
            return count;
        }
}

這段處理能夠說很是容易了,對resource流進行再封裝,封裝爲Docment對象,而後解析並註冊這個doc中的bean對象,返回定義的bean的個數。在Spring3.1以前上面這個方法中還要驗證加載Xml是否符合規範。而Spring5.x以後Spring將驗證的工做放到了獲取Document中。

3、獲取Document

看一下Document doc = doLoadDocument(inputSource, resource);這個方法的源碼:

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
        return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                getValidationModeForResource(resource), isNamespaceAware());
    }

在這個方法中作了三件事:

  1. getEntityResolver();這個方法將根據當前的resource建立一個ResourceLoader實例,而後根據這個對ResourceLoader進行封裝,封裝爲EntityResolver實例, 這個EntityResolver的做用是進行處理實體映射。
  2. getValidationModeForResource(); 這個方法的做用是獲取資源的驗證模式,經過自動或手動的方式對已經加載到的資源進行檢驗。這裏是真正對xml文件進行驗證的地方。
  3. isNamespaceAware(); 這個方法用來判斷加載的xml文件是否支持明明空間。

實現上面方法的類繼承了這個接口:DocumentLoader,而且實現了這個接口中的惟一的抽象:

/**
 * Load a {@link Document document} from the supplied {@link InputSource source}.
 * @param inputSource the source of the document that is to be loaded
 * @param entityResolver the resolver that is to be used to resolve any entities
 * @param errorHandler used to report any errors during document loading
 * @param validationMode the type of validation
 * {@link org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_DTD DTD}
 * or {@link org.springframework.util.xml.XmlValidationModeDetector#VALIDATION_XSD XSD})
 * @param namespaceAware {@code true} if support for XML namespaces is to be provided
 * @return the loaded {@link Document document}
 * @throws Exception if an error occurs
 */

Document loadDocument(

InputSource inputSource, EntityResolver entityResolver,
    ErrorHandler errorHandler, int validationMode, boolean namespaceAware)
    throws Exception;

那麼詳細講一下上面說起的EntityResolver, 若是SAX(Simple API for XML:簡單的來說,它的做用就是不去構建DOM,而是以應用程序的方式以最有效率的方式實現XML與應用實體之間的映射;固然還有一種方式是解析DOM,具體兩種方式,我也沒有作過相應深刻探究)應用驅動程序須要實現自定義的處理外部實體,在必須實現此接口並經過某種方式向SAX驅動器註冊一個實例。這須要根據XML頭部的DTD中的網絡地址下載聲明並認證,而EntityResolver實際上就是在提供一個尋dtd找聲明的方法,這樣就能夠在項目中直接定義好聲明,而經過本地尋找的方式避免了網絡尋找的過程,編譯器也避免了在網絡延遲高或沒有網絡的狀況下報錯。

4、解析及註冊BeanDefinitions

當文件轉換爲Document後,接下來提取及註冊Bean就是咱們後頭的重頭戲。事實上,這一部份內容並不會在咱們的使用中出現了。

一樣在XmlBeanDefinitionReader這個類中,能夠發現隨着Docment的獲取完成後,直接作的是下面的這個事情registerBeanDefinitions();

/**
 * Register the bean definitions contained in the given DOM document.
 * Called by {@code loadBeanDefinitions}.
 * <p>Creates a new instance of the parser class and invokes
 * {@code registerBeanDefinitions} on it.
 * @param doc the DOM document
 * @param resource the resource descriptor (for context information)
 * @return the number of bean definitions found
 * @throws BeanDefinitionStoreException in case of parsing errors
 * @see #loadBeanDefinitions
 * @see #setDocumentReaderClass
 * @see BeanDefinitionDocumentReader#registerBeanDefinitions
 */
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
    int countBefore = getRegistry().getBeanDefinitionCount();
    documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
    return getRegistry().getBeanDefinitionCount() - countBefore;
}

做用在註釋中寫的很清楚,註冊DOM文檔(Spring的配置信息中. 也就是解析後的xml)中包含的bean。

  1. 首先建立一個bean定義文檔讀取器,這個對象是根據DefaultBeanDefinitionDocumentReader的class經過反射的方式來建立的,

DefaultBeanDefinitionDocumentReader實現了BeanDefinitionDocumentReader這個接口,這裏惟一的抽象方法就是registerBeanDefinitions(Document doc, XmlReaderContext readerContext);,這也就意味着上面的第三行代碼是一個天然的應用。

提示:這裏的doc參數就是經過以前的doLoadDocument方法得到的,而這很好的應用了面向對象的單一職責原則, 將轉換爲Docment的複雜過程交給一個單一的類處理,而這個類就是BeanDefinitionDocumentReader, 事實上這是一個接口,而具體的實例化是在createBeanDefinitionDocumentReader這個方法中完成的。
  1. getRegistry();的做用其實是得到一個BeanDefinitionRegistry對象。下面的圖片是程序最開始時,容器開始實現時候的代碼,這裏能夠看到BeanDefinitionRegistry這個接口。注意這裏建立的BeanDefinitionRegistry是final的,也就是這裏獲取的是Spring發現的全部的bean個數,是不準改變的, 熟悉設計模式的同窗確定知道,這個BeanDefinitionRegistry是一個單例的
  2. 而接下來作的就是就是,記錄全部對咱們的資源文件進行加載,這裏是真正解析xml文件並加載的地方,而這個邏輯就是那麼簡單了, 先統計當前的bean defintions個數而後加載一些bean定義進來,而後在統計bean 的個數,而後用後來的減去開始的就是加載的。沒錯了,就是學前班加減法。

到這裏我已經不想探究xml文件是如何讀取的了,若是想看的話,能夠去看下一篇《Spring源碼一(容器的基本實現2)》!

相關文章
相關標籤/搜索