這一節咱們來討論IOC容器到底作了什麼。java
仍是借用以前的那段代碼spring
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容器中的映射)來描述的。app
IOC初始化this
如今咱們經過源碼來分析並驗證:編碼
首先進入它的構造方法,這個構造方法調用其餘的構造方法spa
沒錯,就是這個方法啦,參數跟上圖同樣。這裏有三個方法:3d
super();
setConfigLocation();
refresh();
來看一下這三個方法:
一、資源定義
①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中。
二、資源加載
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在加載或解析錯誤的狀況下
*/
三、資源註冊
在註冊以前須要將讀取到的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();