1.3淺談Spring(IOC容器的實現)
這一節我們來討論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容器的實現)