Spring源碼2 - Bean的加載1

願你歸來時,仍是少年

Spring的模塊劃分參考上一篇:Spring源碼1 -Spring模塊總覽

有些書籍在開篇時,會讓我們去下載Spring的源碼,不過我覺得用maven直接創建項目,然後再引入需要的包,用maven下載源碼也挺方便的,這裏就不介紹怎麼去下整個源碼了,帶着一些問題從一個最簡單的例子開始。

1. 第一個例子


測試功能:先定義一個最簡單的javaBean,然後通過xml的方式進行配置,並在程序中讀取並使用該bean


簡單分析下,這裏只用到了Spring最基礎的功能,即spring-beans模塊,那麼,我們新建一個項目,引入spring-beans模塊即可。這裏爲了測試還引入了junit,maven項目的pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.test</groupId>
    <artifactId>springStudy</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-framework-bom</artifactId>
                <version>5.1.8.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
    </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
</project>

上面的<dependencyManagement> 依賴管理在上一節有提到過,spring爲了減少版本混亂引起的問題,提供了一個依賴管理的bom,定義了一個Spring版本的所有內部組件的版本,下面在<dependencies>中直接引用spring-beans模塊即可

上面的模塊引入之後,直接在idea中maven裏面把這個模塊的源碼下下來即可,後面調試的時候就可以看到帶註釋的源碼了

 

定義一個javaBean,下面看到這個bean只有一個field,和getter&setter

public class MyTestBean {

    private String testStr = "testStr";

    public String getTestStr() {
        return testStr;
    }

    public void setTestStr(String testStr) {
        this.testStr = testStr;
    }
}

新建一個配置文件,引入這個bean

spring-beans.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">


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

下面是測試類,測試一下定義的bean是否可以正常使用

package basic;

import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * Describe ***
 * Created by zhanjp on 2018/6/7
 */
public class BeanFactoryTest {

    @Test
    public void testSimpleLoad(){
        BeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-beans.xml"));
        MyTestBean bean = (MyTestBean) bf.getBean("myTestBean");
        Assert.assertEquals("testStr", bean.getTestStr());
    }
}

運行後,可以看到測試正常

可以看到程序的大體邏輯爲,告訴Spring要加載哪些beans,然後Spring根據配置生成了一個Bean的工廠,後面獲取Bean是通過這個工廠類得到的。

我們沒有去new一個實例,而是把控制權轉給了spring

下面看看Spring是怎麼把我們定義的配置文件,解析爲一個BeanFactory的。

2. 追蹤源碼

2.1 代碼1

new ClassPathResource("spring-beans.xml")

首先這句代碼,字面上理解就是加載一個類路徑下的資源文件,進入源碼

註釋說明了這裏創建了一個ClassPathResource,並使用一個thread context 類加載器去加載這個資源,即最後一行的ClassUtils.getDefaultClassLoader()

關於類加載器,可以看我的另外一篇博客https://mp.csdn.net/postedit/98119054

一般的類加載器,大部分採用雙親委派機制,而這裏使用了一個線程上下文類加載器,是一個破壞雙親委派機制的做法,因爲spring要讀取我們程序裏面的資源

到此爲止,我們可以看到ClassPathResource這個類做的事情還是比較簡單的,把我們傳的參數整理了一下,然後存到對象的屬性裏面,和生成了一個class context 的類加載器。

那麼ClassPathResource這個類出了hold住這兩個對象,還要做什麼功能呢?看下類結構圖

我們進入Resource接口中看一眼,它定義了所有Resource類提供的方法

內容不是很多,我們大概看方法名瞭解一下,然後繼續分析後面的代碼

2.2 代碼2

這裏初始化了一個XmlBeanFactory對象,先了解下XmlBeanFactory

類結構圖:

從類圖來看,這個類相當的複雜,頂層有不少的接口,BeanFactory、SingletenBeanRegistry、AliasRegistry,但是可以看到XmlBeanFactory直接繼承於DefaultListableBeanFactory,這個類對上面的接口做了大量的實現,只留了一小部分工作給XmlBeanFactory。

另外,可以看到XmlBeanFactory已經不被推薦使用了,類上面的註釋告訴我們,從Spring3.1開始,更推薦使用DefaultListableBeanFactory和XmlBeanDefinitionReader這兩個類。

由於上面的類過多且龐大,暫時不細緻的去說,先看看XmlBeanFactory幹了什麼。

這裏我們可以看到,XmlBeanFactory做的事情也很簡單,最關鍵的一行代碼,

this.reader.loadBeanDefinitions(resource);

通過一個Resource去加載Bean的定義。這個過程委託給了XmlBeanDefinitionReader這個類,而這個類的構造參數是this,說明這個Reader也依賴了當前的BeanFactory。

看到這裏有些疑問了,XmlBeanFactory和XmlBeanDefinitionReader互相依賴,Factory構造的時候需要reader屬性,而Reader在構建的時候又需要XmlBeanFactory。

實際上reader只是一個普通的屬性,而非靜態屬性,所以,當類初始化完成後,this已經存在,可以被用來生成XmlBeanDefinitionReader。如果reader屬性是個靜態屬性,就不能這麼玩了。可以看下面

下面進入XmlBeanDefinitionReader類看一看,還是先看結構圖

頂層有兩個接口,EnvironmentCapable和BeanDefinitionReader,前一個裏面只有一個方法,獲取當前組件關聯的Environment,主要看一下第二個接口

主要包括,獲取BeanDefinition註冊表、獲取資源加載器、獲取Bean的類加載器、獲取Bean的名稱生成器和加載Bean的定義的幾個方法。

而XmlBeanDefinitionReader這個類就比較複雜了,畢竟從XML裏面加載Bean的大部分代碼都在這裏,先跟到代碼裏看看

loadBeanDefinition方法沒什麼特殊的,就一個Resouce參數,轉成了EncodedResouce,這個類裏面主要是對特殊的編碼進行處理,而我們沒有做什麼特殊編碼的函數,可以直接先把當成一個Resouce來看。

直接看320行代碼,

resourcesCurrentlyBeingLoaded的初始化如下

NamedThreadLocal只是ThreadLocal的一個簡單的子類,多了一個name的屬性,大概是描述這個ThreadLocal變量是幹嘛用的。

可以看出,這個變量是爲了保存已經加載的resouce資源。

繼續往下看

325行,Spring嘗試把當前加載的資源文件放入已加載集合中,由於可能會有循環的import,碰到互相import的情況,這裏就會直接拋出異常了。可以看出這個集合的作用是防止循環引用變成死循環。

那下面可以看到最重要的地方了,

先從resouce中獲取輸入流,還記得 2.1 new ClassPathResouce的時候,還初始化了一個線程上下文類加載器嗎?進入getInputStream,可以看到

隱藏的還挺深的,這也是爲什麼看源碼這麼讓人頭疼的地方,畢竟不是自己寫的。。

好了,繼續,根據這個InputStream,332行初始化了一個InputSource,並設置了編碼。額,我們並沒有給什麼編碼,就認爲InputSource就是個InputStream吧。

下面就進入了336行

doLoadBeanDefinitions(inputSource, encodedResource.getResource());

開始加載bean的定義!

跟進代碼,這裏先是根據輸入流和Resouce初始化了一個Document類,這就是根據Xml解析出來的對象了。下面一行就是根據這個document解析並把Bean的定義註冊到Registry中了,並返回加載的數量。

這也是Spring加載Bean的最重要的兩個邏輯了

  • Spring如何去解析Xml
  • Spring如何去註冊Bean

3. Spring如何解析Xml?

首先進入代碼

Spring把加載XML資源的活全都委託給了documentLoader這個對象,這個對象的類型是DefaultDocumentLoader,實現了DocumentLoader接口。而DocumentLoader接口只有這一個實現類,可見這個DocumentLoader就是專門幹加載Xml資源這個活的。

接下來看看DefaultDocumentLoader類裏面的代碼

這裏的類結構大概可以看到,其實spring主要的解析Xml的方法,就是loadDocument這個方法,另外包含了兩個protected方法,和兩個靜態變量,

這兩個靜態變量分別用來配置如何驗證schema語言,和表示XSD schema語言的。從描述來看,是給JAXP驗證XML用的兩個屬性。

真正去解析xml的代碼,先創建了一個DocumentBuilderFactory然後再創建了一個DocumentBuilder,這兩個類都是javax.xml.parser包下面的,可見spring用來解析xml正式依賴了java系統的javax.xml.parser包,而這個包正是JAXP下面的,什麼是JAXP呢?就是Java api for xml processing,爲處理xml而制定的一套javaAPI。下面是JAXP的官方文檔:

https://docs.oracle.com/javase/8/docs/technotes/guides/xml/jaxp/

文檔的最下面還有一些jaxp提供的一些教程,這裏也貼一下

https://docs.oracle.com/javase/tutorial/jaxp/index.html

可以看到上面的代碼裏,需要的5個參數都是jaxp的參數,所以Spring解析XML而寫的DefaultDocumentLoader是爲了解析XML而專門包裝的工具類。

XML涉及的東西過多,這裏不再詳細介紹,有機會後面再寫一些使用JAXP的例子。相信有一些小例子更容易使我們理解Spring的解析XML的內容。

下一篇博客繼續

 

 

 

 

 

 

 

 

 

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