1. 程式人生 > >1.3淺談Spring(IOC容器的實現)

1.3淺談Spring(IOC容器的實現)

tap 就是 parser pojo file abstract throw cdd moni

這一節我們來討論IOC容器到底做了什麽。

還是借用之前的那段代碼

ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");
Car car =app.getBean(Car.class);
System.out.println(car.getBrand()+","+car.getDesc());

這裏ClassPathXmlApplicationContext是如何加載beans.xml的呢?

它到底做了哪些事情?

1.資源定位 2.資源加載 3.資源註冊

再次之前,補充一點:SpringIOC容器管理了我們定義的各種Bean對象及其相互的關系,Bean對象在Spring實現中是以BeanDefinition(Bean定義資源文件中配置的POJO對象在Spring IoC容器中的映射)來描述的。

IOC初始化

現在我們通過源碼來分析並驗證:

技術分享圖片

首先進入它的構造方法,這個構造方法調用其他的構造方法

技術分享圖片

沒錯,就是這個方法啦,參數跟上圖一樣。這裏有三個方法:

super();

setConfigLocation();

refresh();

來看一下這三個方法:

1、資源定義

①super();//資源加載器的配置

執行的是AbstractApplicationContext的構造方法

技術分享圖片技術分享圖片技術分享圖片

這裏的this,就是ClassPathXmlApplicationContext,它間接的繼承自AbstractApplication Context,而AbstractApplicationContext又繼承了DefaultResourceLoader,故它本身就是個ResourceLoader

技術分享圖片

②setConfigLocation()//資源定位

技術分享圖片

處理文件路徑為一個字符串的情況

技術分享圖片

處理多個多個資源文件字符串數組

StringUtils.tokenizeToStringArray(location, CONFIG_LOCATION_DELIMITERS)
//String CONFIG_LOCATION_DELIMITERS = ",; \t\n";

這個方法是用來解析我們給的location,因為在創建ClassPathXmlApplicationContext時,我們可以傳入多個Xml的配置,並用分號隔開。如:

ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("beans.xml;spring.xml");

這時候就需要來解析這個String了,當然這個String有多種寫法,不止用分號這麽簡單。所以需要來解析一下。

this.configLocations[i] = resolvePath(locations[i]).trim();

這個resovlePath是用來解析一些占位符的,由於這些文件可能是 classpath , filesystem ,或者是 URL 網絡資源, servletContext 等。所以可能會存在占位符。

具體的解析過程在PropertyPlaceholderHelper的parseStringValue中。

2、資源加載

AbstractApplicationContext的refresh()是一個模版方法,它就是對IOC容器進行初始化並且對資源進行載入。其中obtainFreshBeanFactory()就是對"Bean"資源加載的關鍵。

技術分享圖片

startupShutdownMonitor用於刷新和銷毀的同步標記

①prepareRefresh()//準備刷新

技術分享圖片

準備此上下文以進行刷新,設置其啟動日期和活動標誌以及執行屬性源的任何初始化。

②obtainFreshBeanFactory()//資源加載

技術分享圖片

先看refreshBeanFactory();//刷新BeanFactory

技術分享圖片

判斷是否存在BeanFacotry(即IOC容器),如果存在就銷毀,因為IOC容器是單例的。只能存在一個。

這裏createBeanFactory創建的是一個默認的DefaultListableBeanFactory

技術分享圖片

customizeBeanFactory();//初始化工廠參數

自定義此上下文使用的內部bean工廠。 為每次refresh()嘗試調用。默認實現應用此上下文的“allowBeanDefinitionOverriding”和“allowCircularReferences”設置(如果已指定), 可以在子類中重寫以自定義任何DefaultListableBeanFactory的設置。

loadBeanDefinitions();//加載BeanDefinitions

這裏調用的是AbstractXmlApplicationContext的loadBeanDefinitions方法

技術分享圖片

這裏傳入了RourceLoader的資源加載器為ClassPathXmlApplicationContext

真正執行是重寫的方法

技術分享圖片

繼續執行重寫方法

技術分享圖片技術分享圖片

真正執行的重寫方法:

技術分享圖片圖-X

這裏首先獲取getResourceLoader即是獲取ClassPathXmlApplication,之前說過它繼承自AbstractApplicationContext,而AbstractApplicationContext又繼承了DefaultResource Loader

IOC容器是如何取到資源的呢?

這裏的getResourceLoader();//取資源加載器

//獲取資源
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);

技術分享圖片

這裏locationPattern用來區分是以多資源文件的形式,還是單資源文件的形式(就像Spring跟SpringMVC同時使用時的情況).

String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

這裏走else分支的else分支(獲取單個資源)

return new Resource[] {getResourceLoader().getResource(locationPattern)};

getResourceLoader()即獲取資源加載器,這裏的就是ClassPathXmlApplicationContext,

getResource()//獲取資源

技術分享圖片

這裏仍然走的是else分支,執行getResourceByPath(location);區分是從 本地,還是URL,還是其他的方式來配置的。

技術分享圖片

技術分享圖片

最終執行ClassPathResource的構造方法

技術分享圖片

最終得到的resource如下圖:

技術分享圖片

在獲取到了Resource之後,我們繼續回到圖-X,看它接下來的操作

int loadCount = loadBeanDefinitions(resources);

最終執行XmlBeanDefinitionReader的loadBeanDefinitions方法:

/**
*從指定的XML文件加載bean定義。
* @param encodedResource XML文件的資源描述符,
*允許指定用於解析文件的編碼
* @return找到的bean定義數
* @throws BeanDefinitionStoreException在加載或解析錯誤的情況下
*/

技術分享圖片

3、資源註冊

在註冊之前需要將讀取到的resource轉換成BeanDefinitions

這裏獲取資源的輸入流,並執行真正執行的方法doLoadBeanDefinitions()

/**
*實際上從指定的XML文件加載bean定義。
* @param inputSource要讀取的SAX InputSource
* @param資源XML文件的資源描述符
* @return找到的bean定義數
* @throws BeanDefinitionStoreException在加載或解析錯誤的情況下
* @see #doLoadDocument
* @see #registerBeanDefinitions
*/

技術分享圖片

這裏doLoadDocument(inputSource,resource)將資源文件轉換成DOM對象

具體轉換過程這裏就不進行細看了,主要使用JAXP。

轉換成DOM之後需要做的就是將DOM轉換成Spring能夠識別的數據結構BeanDefinitions

技術分享圖片

這裏的BeanDefinitionDocumentReader用於解析實際的DOM文檔。

這裏執行DefaultBeanDefinitionDocumentReader的registerBeanDefinitions

技術分享圖片

doRegisterBeanDefinitions();是真正的註冊beanDefinitions的方法

技術分享圖片

這裏有一個用來幫助解析的類:

BeanDefinitionParserDelegate

官方給的解釋是:用於解析XML bean定義的有狀態委托類。 旨在供主解析器和任何擴展BeanDefinitionParsers或BeanDefinitionDecorators使用。

之後是對Spring命名空間的一些檢測。

最後才是真正的解析BeanDefinitions——parseBeanDefinitions

/**
*解析文檔中根級別的元素:
*“import”,“alias”,“bean”。
* @param root文檔的DOM根元素
*/

技術分享圖片

這裏補充一點XML的知識

xml文檔 —————-> Document對象 代表整個xml文檔
節點 —————>Node對象 父類
標簽節點 —————> Element對象 子類
屬性節點 —————> Attribute對象 子類
文本節點 —————>Text對象 子類
NodeList ---------->節點列表集合(Node的集合)

先看這個默認命名空間的解析方法:

parseDefaultElement();

技術分享圖片

這裏分別對<import> <alias> <bean><beans> 用各自的方法進行解析

這裏的順序也是比較講究

先查看是否有<import>標簽,如果有可以先引入其他資源文件到IOC容器中。

然後查看<alias>標簽,如果有先引入別名到IOC容器中(後面會根據是否有id將別名賦值給bean)

然後查看<bean>標簽

最後查看<beans>,可能<beans>裏引入了其他<bean>,故在最後

這裏我們查看對於 <bean>標簽的解析:

processBeanDefinition();

/**
*處理給定的bean元素,解析beanDefinition
*並在註冊表中註冊。
*/

技術分享圖片

首先解析BeanDefinition的基本屬性:

BeanDefinitionParserDelegate裏的parseBeanDefinitionElement();

1.3淺談Spring(IOC容器的實現)