1. 程式人生 > >Spring源碼

Spring源碼

註意 parsec 容器 table ring 沒有 single 分支 使用

1.核心監聽器ContextLoaderListener實現了ServletContextListener監聽項目的啟動調用初始化方法
2.調用父類ContextLoader的initWebApplicationContext
3.先初始化當前ApplicationContext對象,通過servletContext對象查看web.xml下是否配置名為contextClass的參數,若沒配置就默認初始化XmlWebApplicationContext對象
4.調用ContextLoader的configureAndRefreshWebApplicationContext方法,讀取web.xml下配置的contextConfigLocation,隨後把servletContext對象設置為XmlWebApplicationContext的一個屬性,這樣Spring就能在上下文裏輕松拿到ServletContext了。最後調用XmlWebApplicationContext的refresh方法,在這個方法裏,會完成資源文件的加載、配置文件解析、Bean定義的註冊、組件的初始化等核心工作
5.在refresh方法中先構建一個DefaultListableBeanFactory對象,(此對象中維護一個Map<String,Beandefinition>對象來保存Bean的相關信息)
6.接著調用loadBeanDefinitions方法解析配置文件,填充該Map 。
解析的過程:
--首先初始化一個XmlBeandefinitionReader對象(解析配置文件實際靠的是它)
--調用reader對象的loadBeanDefinition,該方法中會利用contextConfigLocation的配置文件路徑去讀取配置文件,若沒該參數則默認去讀取/WEB-INF/applicationContext.xml
--XmlBeandefinitionReader拿到配置文件後,通過sax解析先生存document對象,然後解析document下的Element(即解析一個個標簽)
--當解析的標簽是默認標簽時,默認標簽是命名空間為(/schema/beans下的標簽,<bean>標簽),調用document解析代理的parseDeaultElement方法,解析bean的各種屬性,如name,class,封裝在BeanDefinition對象中,並註冊在DefaultListableBeanFactory的map對象裏
--當解析的標簽不是默認標簽時,如命名空間為(/schema/context下的標簽,常用的標簽如<context:component-scan base-package=""/>),調用document解析代理的parseCustomElement方法,該方法中的NamespaceHandler會根據不同的命名空間,初始化不同的BeanDefinitionParse對象來處理,如<context:component-scan base-package=""/>標簽會生成ComponentScanBeanDefinitionParser來掃描指定包[email protected]

/* */@Compenent等註解的類,把相關的信息封裝在BeanDefinition中。
--調用ComponentScanBeanDefinitionParser的parse方法會去base-package的目錄及子目錄下找到相關的.class文件,[email protected]@Component等註解的類,封裝到Beandefinition中
7.把DefaultListableBeanFactory對象設置為XmlWebApplicationContext的一個屬性。
至此<bean>或註解掃描配置文件的初始化完畢,最後就是在XmlWebApplicationContext的DefaultListableBeanFactory對象屬性中的Map包含了所有要被容器加載的類基本信息
8.根據Map對象裏的配置信息初始化成bean對象。具體對應refresh方法中的finishBeanFactoryInitialization方法,該方法用來初始化所有的單例非延遲加載的對象
9.先去DefaultSingletonBeanRegistry(此類為DefaultListableBeanFactory抽象父類)類中的ConcurrentHashMap中尋找是否緩存有beanName對應的單例對象,若有,表明此單例對象已被實例化過,直接返回。
10.若緩存中沒找到則再判斷當前DefaultListableBeanFactory緩存的Beandefinition的Map的key裏是否包含當前的beanName,若沒包含並存在父容器的話,則嘗試去父容器查找實例化
11.當前DefaultListableBeanFactory緩存的Beandefinition的Map的key裏包含該beanName的話,取出對應的BeanDefinition,獲取其初始化需要依賴的對象數組,然後循壞初始化依賴的對象。
12.依賴的對象初始化完畢後,判斷當前Beandefinition的對象是單例還是多例,兩者的區別在於,單例的(Singleton)被緩存起來,而Prototype是不用緩存的。
13.在單例判斷分支的createBean方法中,如果沒有無參構造器是就生成CGLIB子類,否則就直接反射成實例
14.接著通過populateBean方法註入屬性,(屬性依賴的對象在11中已全部創建)。

-------幾個重要的問題--------
循環依賴問題:對象A依賴對象B,對象B依賴對象A
1.若A、B為單例:
--若A對B的依賴發生在構造參數中,B對A的依賴也在構造參數中,則報錯,觸發條件:A創建時,會在一個表示正在創建的對象的Map緩存中,保存,當A開始創建時(還未完成構造函數執行)就在表示正在創建的Map中註冊自己,執行A的構造函數時,發現依賴B,則去創建B對象,B同理也會在該Map中註冊自己,執行B的構造函數時,發現B也依賴於A(此時A沒有創建完成),於是去創建A對象,發現A在正在創建緩存中已註冊,則報錯。【註意,一個對象創建完成後會在該Map中移除】
--若A對B的依賴是屬性依賴,通過setter方法註入,B同理,由於對象是先執行構造函數,再執行setter方法,並且執行完構造函數後,則把自己緩存在單例緩存池中,這樣調用setter方法時,對象已存在,直接把對象引用賦值給setter即可。不會報錯
--基於上述原理,使用setter代替構造器可以完成單例對象的循壞依賴初始化。
2.若A、B為多例:
由於多例對象不存在緩存,所以不能完成循壞依賴初始化,直接報錯。

解析AOP標簽:
XML解析中,遇到AOP命名空間的標簽後,調用parseCustomElement方法,會找到ConfigBeanDefinitionParser來解析AOP標簽,
解析完標簽後,在bean初始化完後註入屬性之前,會檢查當前bean是否有切入點,如有切入點,則為當前bean生成代理對象,返回代理對象。


-------------------------------------------精簡版--------------------------------------------
1.核心監聽器ContextLoaderListener監聽web項目的啟動,web項目啟動時創建IOC容器XMLWebApplicationContext對象,該對象就是IOC容器
【IOC容器對象XMLWebApplicationContext對象裏面維護了一個DefaultListableBeanFactory,此對象中維護一個Map<String,Beandefinition>對象來保存Bean的定義相關信息,還維護了單例對象緩存池】
2.然後把servletContext對象設置在該容器中,最後調用XMLWebApplicationContext的refresh方法
3.refresh方法中先創建DefaultListableBeanFactory對象,並且調用loadBeanDefinitions方法解析配置文件中的標簽填充到DefaultListableBeanFactory的Map裏去,配置文件的位置會嘗試去web.xml下配置的contextConfigLocation中讀取,若沒配置則默認去讀取/WEB-INF/applicationContext.xml
4.在解析標簽時,不同的命名空間需要用不同的解析類去解析,默認的命名空間是beans,用於解析默認的<bean>標簽,解析<bean>的name、class、propterty、constructer等屬性,封裝在BeanDefinition裏,並註冊到Map裏去
如果遇到<context:component-scan base-package=""/>標簽,會調用對象的解析類去base-package指定的包及子包下有component、service、controller、Respositoty的類,並封裝成BeanDefinition,保存到Map裏。
5.現在XMLWebApplicationContext裏的DefaultListableBeanFactory的Map對象裏就包含了所有XML配置的bean和包含註解的bean的定義信息。
6.隨後開始單例非延遲加載的對象並緩存在緩存池中。
7.每拿到一個BeanName會嘗試去單例緩存中直接獲取bean實例,若獲取不到則創建。
8.創建對象的過程是:取出BeanDefinition,獲取依賴對象,會先創建依賴對象的,然後利用反射創建實例對象。當然期間還涉及到解決循環依賴的問題。
【解決循環依賴:若是在構造函數中的循環依賴:創建時,會把自己加到正在創建的緩存中,創建完成會移除,A->B,A加入正在創建,創建B,B加入正在創建,B又依賴A,A創建,A發現自己已經在正在創建的緩存中了,報錯】
【 setter,調用完無參構造函數,創建完了之後,不等set屬性就把自己提前暴露出去。使其他對象可以引用自己】
9.接著註入屬性

Spring源碼