最近在學習Spring IOC基本源碼,瞭解整個的流程。代碼太繞了,看的時候簡單記錄了下,現在分享下~
1、首先Spring簡單總結
2、簡單步驟
3、XmlBeanFactory
- public class XmlBeanFactory extends DefaultListableBeanFactory{
- private final XmlBeanDefinitionReader reader;
-
- public XmlBeanFactory(Resource resource)throws BeansException{
- this(resource, null);
- }
- public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory)
- throws BeansException{
- super(parentBeanFactory);
- this.reader = new XmlBeanDefinitionReader(this);
- this.reader.loadBeanDefinitions(resource);
- }
- }
4、FileSystemXmlApplicationContext IOC容器初始化
基本步驟:
- 資源定位,用字符串或者數組配置BeanDefinition文件(確定文件地址)
- 設置資源加載器resourceLoader,AbstractApplicationContext(繼承了DefaultResourceLoader)
- 開始初始化IOC容器
- 初始化的入口在容器實現中的 refresh()調用來完成,實際調用的AbstractRefreshableApplicationContext的refreshBeanFactory()方法
- 已有BeanFacotry(ioc容器)則銷燬,關閉,重新創建ioc容器
- 容器定製化(啓動參數,開啓註解等),載入Bean定義(BeanDefinition)
- 對 bean 定義載入 IOC 容器使用的方法是 loadBeanDefinition,其中的大致過程如下
- 通過 ResourceLoader 來完成資源文件位置的定位,DefaultResourceLoader 是默認的實現,同時上下文本身就給出了 ResourceLoader 的實現。
- 可以從類路徑,文件系統, URL 等方式來定位資源位置,並抽象成相應的Resource 。
- 如果是 XmlBeanFactory作爲 IOC 容器,那麼需要爲它指定 bean 定義的資源,也就是說 bean 定義文件是通過抽象成 Resource 來被 IOC 容器處理的。
- 容器通過 BeanDefinitionReader來完成定義信息的解析和 Bean 信息的註冊,往往使用XmlBeanDefinitionReader 來解析 bean 的 xml 定義文件(Resource),轉換成Document對象。
- 至此,IOC加載讀入了資源文件,並轉換成了document對象。下面解析Document對象爲Bean,並註冊到容器中。
- 實際的處理過程是委託給 BeanDefinitionParserDelegate 來完成的,它定義了Spring BeanDefinition XML文件的各種元素,以及解析各種元素的方法。
- 如果document對象使用了Spring默認的XML命名空間,則按照spring的解析規則,解析<import>導入元素,解析<Alias>別名元素以及普通的<Bean>元素。否則使用用戶自定義的解析規則。
- 將Document對象以及Resource文件轉換爲Spring IoC所識別的數據結構——BeanDefinition,它是Bean定義資源文件中配置的POJO對象在Spring IoC容器中的映射。
- 容器解析得到 BeanDefinition 以後,需要把它在 IOC 容器中註冊,這由 IOC 實現 BeanDefinitionRegistry 接口來實現。
- 註冊過程就是在 IOC 容器內部維護的一個HashMap 來保存得到的 BeanDefinition。這個 HashMap 是 IoC 容器持有 bean 信息的場所,以後對 bean 的操作都是圍繞這個HashMap 來實現的。註冊過程中需要線程同步,註冊後重載BeanDefinition的緩存。
- 註冊完成,IoC容器就建立了整個Bean的配置信息,這些BeanDefinition信息已經可以使用,並且可以被檢索,IoC容器的作用就是對這些註冊的BeanDefinition進行處理和維護。
詳細步驟:
- 父類容器的構造方法AbstractApplicationContext(ApplicationContext parent)爲容器設置好Bean資源加載器 PathMatchingResourcePatternResolver
- setConfigLocations(configLocations)方法設置Bean定義資源文件的定位路徑。既可以使用一個字符串來配置多個Spring Bean定義資源文件,也可以使用字符串數組
- refresh(); 載入Bean定義(見3)
- synchronized (this.startupShutdownMonitor)
- 調用容器準備刷新的方法,獲取容器的當時時間,同時給容器設置同步標識 prepareRefresh();
- 告訴子類啓動refreshBeanFactory()方法,啓動Bean定義資源文件的載入(見4)
- 爲BeanFactory配置容器特性,例如類加載器、事件處理器等 prepareBeanFactory(beanFactory);
- 爲BeanFactory初始化信息源,件傳播器等等
- 先判斷BeanFactory是否存在,如果存在則先銷燬beans並關閉beanFactory
- 接着創建DefaultListableBeanFactory,完成定製化(啓動參數,開啓註解等)
- 委派子類AbstractXmlApplicationContext調用loadBeanDefinitions(beanFactory)裝載bean 定義
- 用beanFactory創建了XmlBeanDefinitionReader
- 將自身(XmlBeanDefinitionReader )設置爲Reader 的ResourceLoader
- 用getConfigLocations方法獲得資源位置location
- 用Reader調用XmlBeanDefinitionReader 父類的loadBeanDefinitions方法,參數爲location (見6)
- 獲得ResourceLoader(即5中的XmlBeanDefinitionReader)
- 用ResourceLoader調用父類DefaultResourceLoader方法getResources(location)獲得BeanDefinitionResources(或者數組resources) (見7)
- 委派子類XmlBeanDefinitionReader調用loadBeanDefinitions(resources)(見8)
- ClassPathResource
- UrlResource
- ResourceByPath
- 對XML進行特殊編碼處理,並調用重載方法loadBeanDefinitions(new EncodedResource(resource))
- 將encodedResource轉換爲XML的解析源InputSource
-
調用doLoadBeanDefinitions(inputSource, encodedResource.getResource())完成了
- 將XML文件轉換爲DOM對象Document(見9)
- 啓動對Bean定義解析registerBeanDefinitions(doc, resource)(見10)
- 創建文件解析器工廠DocumentBuilderFactory
- 創建文檔解析器DocumentBuilder(factory, entityResolver, errorHandler)
- 解析Spring的Bean定義資源返回Document對象 builder.parse(inputSource)
- 創建BeanDefinitionDocumentReader來對xml格式的BeanDefinition解析
- 解析過程入口,委派實現類DefaultBeanDefinitionDocumentReader完成registerBeanDefinitions(doc, createReaderContext(resource)) (見11)
- 統計解析的Bean數量 getRegistry().getBeanDefinitionCount() - countBefore
- 獲取Document的根元素doc.getDocumentElement();
- 調用方法createHelper(readerContext, root)獲得BeanDefinitionParserDelegate,定義了Spring Bean定義XML文件的各種元素
- 解析Bean定義之前,進行自定義的解析 preProcessXml(root);
- 調用方法parseBeanDefinitions(root, delegate);從Document的根元素開始解析Bean定義
- 在解析Bean定義之後,進行自定義的解析 postProcessXml(root);
- Bean定義的Document對象使用了Spring默認的XML命名空間,遍歷Document對象根元素的所有子節點,如果是XML元素節點,調用parseDefaultElement(ele, delegate);否則則使用用戶自定義的解析規則解析元素節點
- 根節點沒有使用Spring默認的命名空間,則使用用戶自定義的解析規則解析Document根節點
- 如果元素節點是<Import>導入元素,進行導入解析 importBeanDefinitionResource(ele)
- 如果元素節點是<Alias>別名元素,進行別名解析 processAliasRegistration(ele);
- 元素節點既不是導入元素,也不是別名元素,即普通的<Bean>元素,按照Spring的Bean規則解析元素 processBeanDefinition(ele, delegate);(見12)
- 獲取給定的導入元素的location屬性 ele.getAttribute(RESOURCE_ATTRIBUTE);
- 使用系統變量值解析location屬性值 location = SystemPropertyUtils.resolvePlaceholders(location);
- 導入元素的location是否是絕對路徑
- 絕對路徑 使用資源讀入器加載給定路徑的Bean定義資源 getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
- 相對路徑 將給定導入元素的location封裝爲相對路徑資源 Resource relativeResource =
- getReaderContext().getResource().createRelative(location);
- 相對路徑資源存在 使用資源讀入器加載Bean定義資源,不存在 根據Spring IoC容器資源讀入器的基本路徑加載
- 在解析完<Import>元素之後,發送容器導入其他資源處理完成事件
- 獲取<Alias>別名元素中name的屬性值 String name = ele.getAttribute(NAME_ATTRIBUTE);
- 獲取<Alias>別名元素中alias的屬性值 String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
- 都不爲空時,向容器的資源讀入器註冊別名 getReaderContext().getRegistry().registerAlias(name, alias);
- 在解析完<Alias>元素之後,發送容器別名處理完成事件
- delegate.parseBeanDefinitionElement(ele);對Document對象中<Bean>元素的解析,獲得對BeanDefinition的封裝類,BeanDefinitionHolder(見12-15)
- 向Spring IoC容器註冊解析得到的Bean定義 BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()); (見16)
- 完成向Spring IoC容器註冊後發送註冊事件,getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
- 獲取<Bean>元素中的id屬性值、name、alias,將所有name屬性值存放到別名中
- 如果<Bean>元素中沒有配置id屬性時,將別名中的第一個值賦值給beanName
- 詳細對<Bean>元素中配置的Bean定義解析beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
- 如果beanDefinition 中不包含beanName(即沒有id、別名、name),且沒有子<Bean>,需要生成一個唯一的baenName並註冊,有子<Bean>則使用別名+類名後綴作爲baenName並註冊。
- 獲得class名字,parent屬性(如果有)
- 根據<Bean>元素配置的class名稱和parent屬性值創建BeanDefinition AbstractBeanDefinition bd = createBeanDefinition(className, parent);
- 解析一些屬性,如配置的單態(singleton)屬性,解析description信息,meta(元信息)等
- 解析<Bean>元素的<property>設置(見13)
- 獲取<Bean>元素中所有的子元素,如果子元素是<property>,則調用方法 解析<property>子元素
- 通過一個parseState隊列存儲所有property鍵值對,this.parseState.push(new PropertyEntry(propertyName));
- 獲取<property>元素的名字,如果一個Bean中已經有同名的property存在,則不進行解析,直接返回。即如果在同一個Bean中配置同名的property,則只有第一個起作用。
- 解析獲取property的值,方法 Object val = parsePropertyValue(ele, bd, propertyName);
- 根據property的名字和值創建property實例
- 獲取<property>,只能是其中一種類型:ref,value,list等
- 如果屬性是ref,創建一個ref的數據對象RuntimeBeanReference,封裝了ref信息。一個指向運行時所依賴對象的引用RuntimeBeanReference ref = new RuntimeBeanReference(refName);並調用ref.setSource(extractSource(ele));與當前的property對象關聯。
- 如果屬性是value,創建一個value的數據對象TypedStringValue,封裝了value信息。TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE)); valueHolder.setSource(extractSource(ele));與當前的property對象關聯
- 如果當前<property>元素還有子元素,解析<property>的子元素(見14)
- 如果<property>沒有使用Spring默認的命名空間,則使用用戶自定義的規則解析
- 如果子元素是bean,則使用解析<Bean>元素的方法解析
- 如果子元素是ref,ref中只能有以下3個屬性:bean、local、parent。bean引用其他解析的Bean的名稱可以不再同一個Spring配置文件中;local屬性值,引用同一個Xml文件中配置;parent屬性值,引用父級容器中的Bean。創建ref類型數據,與當前的property對象關聯
- 如果子元素是value、null、array、list……,使用相應的方法解析,生成對應的數據對象,比如ManagedList、ManagedArray、ManagedSet等,這些Managed類是Spring對象BeanDefiniton的數據封裝(見15)
- 獲取數據類型
- 獲取所有子節點 nl
- 新建ManagedList<Object> target,設置類型
- 具體的<list>元素解析 parseCollectionElements(nl, target, bd, defaultElementType); <array>、<list>和<set>都使用該方法解析
- 獲取解析的BeanDefinition的名稱
- 向IoC容器註冊BeanDefinition registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition())(見17)
- 如果解析的BeanDefinition有別名,向容器爲其註冊別名
- 使用一個HashMap的集合對象存放IoC容器中註冊解析的BeanDefinition
- 校驗解析的BeanDefiniton,失敗拋出異常
- 註冊的過程中需要線程同步 synchronized (this.beanDefinitionMap)
- 檢查是否有同名的BeanDefinition已經在IoC容器中註冊,已經註冊不允許覆蓋就拋出異常,允許覆蓋則覆蓋。IoC容器中沒有已經註冊同名的Bean,按正常註冊流程註冊。
- 註冊後重置所有已經註冊過的BeanDefinition的緩存