做爲一個常用Spring的後端程序員,小編很早就想完全弄懂整個Spring框架了!但它總體是很是大的,全部繼承圖很是複雜,加上小編修行尚淺,顯得力不從心。不過,男兒在世當立志,今天就先從Spring IOC容器的初始化開始提及,即便完成不了對整個Spring框架的徹底掌握,也不丟人,由於小編動手了,穩住,咱能贏!html
下面說一些閱讀前的建議:java
選中一個類名,先ctrl+shift+alt+U,再ctrl+alt+B,而後回車便可
很少,就一行代碼,以下圖:程序員
這句是Spring初始化的代碼,雖然只有一句代碼,但內容賊多!web
這樣子,小編先理清下思路,一步一步來:spring
applicationContext.xml
,這是個資源文件,因爲咱們的bean
都在裏邊進行配置定義,那Spring總得對這個文件進行讀取並解析
吧!因此Spring中有個模塊叫Resource
模塊,顧名思義,就是資源
嘛!用於對全部資源xml、txt、property
等文件資源的抽象。關於對Resource
的更多知識,能夠參考下邊兩篇文章:下面先貼一張小編生成的類圖(圖片有點大,不知道會不會不清晰,若是不清晰能夠按照上面說的idea生成方法去生成便可)
:後端
能夠看到Resource
是整個體系的根接口,點進源碼能夠看到它定義了許多的策略方法
,由於它是用了策略模式
這種設計模式,運用的好處就是策略接口/類
定義了同一的策略,不一樣的子類有不一樣的具體策略實現,客戶端調用時傳入一個具體的實現對象好比UrlResource或者FileSystemResource
給策略接口/類Resource
便可!設計模式
全部策略
以下:緩存
策略模式
,那麼問題來了,如今表示資源的東西有了,那麼是怎麼把該資源加載進來呢?因而就有了下面的ResourceLoader
組件,該組件負責對Spring資源的加載,資源指的是xml
、properties
等文件資源,返回一個對應類型的Resource
對象。。UML圖以下:從上面的UML圖能夠看出,ResourceLoader
組件其實跟Resource
組件差很少,都是一個根接口,對應有不一樣的子類實現,好比加載來自文件系統的資源,則可使用FileSystemResourceLoader
,加載來自ServletContext
上下文的資源,則可使用ServletContextResourceLoader
。 還有最重要的一點,從上圖看出,ApplicationContext
,AbstractApplication
是實現了ResourceLoader
的,這說明什麼呢?說明咱們的應用上下文ApplicationContext
擁有加載資源的能力,這也說明了爲何能夠經過傳入一個String resource path
給ClassPathXmlApplicationContext("applicationContext.xml")
就能得到xml文件資源的緣由了!清晰了嗎?nice!
ResourceLoader
,也擁有了對資源的描述Resource
,可是咱們在xml文件中聲明的<bean/>
標籤在Spring又是怎麼表示的呢?注意這裏只是說對bean
的定義,而不是說如何將<bean/>
轉換爲bean
對象。我想應該不難理解吧!就像你想表示一個學生Student
,那麼你在程序中確定要聲明一個類Student
吧!至於學生數據是從excel
導入,或者程序運行時new
出來,或者從xml
中加載進來這些都不重要,重要的是你要有一個將現實中的實體表示爲程序中的對象的東西,因此<bean/>
也須要在Spring中作一個定義!因而就引入一個叫BeanDefinition
的組件,UML圖以下:下面講解下UML圖:
首先配置文件中的<bean/>
標籤跟咱們的BeanDefinition
是一一對應的,<bean>
元素標籤擁有class
、scope
、lazy-init
等配置屬性,BeanDefinition
則提供了相應的beanClass
、scope
、lazyInit
屬性。
其中
RootBeanDefinition
是最經常使用的實現類,它對應通常性的<bean>
元素標籤,GenericBeanDefinition
是自2.5
之後新加入的bean
文件配置屬性定義類,是一站式服務類。在配置文件中能夠定義父<bean>
和子<bean>
,父<bean>
用RootBeanDefinition
表示,而子<bean>
用ChildBeanDefiniton
表示,而沒有父<bean>
的<bean>
就使用RootBeanDefinition
表示。AbstractBeanDefinition
對二者共同的類信息進行抽象。Spring
經過BeanDefinition
將配置文件中的<bean>
配置信息轉換爲容器的內部表示,並將這些BeanDefiniton
註冊到BeanDefinitonRegistry
中。Spring
容器的BeanDefinitionRegistry
就像是Spring
配置信息的內存數據庫,主要是以map
的形式保存,後續操做直接從BeanDefinitionRegistry
中讀取配置信息。通常狀況下,BeanDefinition
只在容器啓動時加載並解析,除非容器刷新或重啓,這些信息不會發生變化,固然若是用戶有特殊的需求,也能夠經過編程的方式在運行期調整BeanDefinition
的定義。
ResourceLoader
,也擁有了對資源的描述Resource
,也有了對bean
的定義,咱們不由要問,咱們的Resource
資源是怎麼轉成咱們的BeanDefinition
的呢?所以就引入了BeanDefinitionReader
組件,Reader嘛!就是一種讀取機制,UML圖以下:從上面能夠看出,Spring 對reader進行了抽象,具體的功能交給其子類去實現,不一樣的實現對應不一樣的類,如PropertiedBeanDefinitionReader
,XmlBeanDefinitionReader
對應從Property和xml的Resource解析成BeanDefinition
。
其實這種讀取數據轉換成內部對象的,不只僅是Spring專有的,好比:Dom4j解析器
SAXReader reader = new SAXReader(); Document doc = reader.read(url.getFile());
//url是一個URLResource對象 嚴格來講,都是Reader體系吧,就是將統一資源數據對象讀取轉換成相應內部對象。
BeanDefinition
後,你還必須將它們註冊到工廠中去,因此當你使用getBean()
方法時工廠才知道返回什麼給你。還有一個問題,既然要保存註冊這些bean
,那確定要有個數據結構充當容器吧!沒錯,就是一個Map
,下面貼出BeanDefinitionRegistry
的一個實現,叫SimpleBeanDefinitionRegistry
的源碼圖:BeanDefinitionRegistry
的UML圖以下:
從圖中能夠看出,BeanDefinitionRegistry
有三個默認實現,分別是SimpleBeanDefinitionRegistry
,DefaultListableBeanFactory
,GenericApplicationContext
,其中SimpleBeanDefinitionRegistry
,DefaultListableBeanFactory
都持有一個Map,也就是說這兩個實現類把保存了bean。而GenericApplicationContext
則持有一個DefaultListableBeanFactory
對象引用用於獲取裏邊對應的Map。 在DefaultListableBeanFactory
中
在GenericApplicationContext
中
ApplicationContext
上下文基本直接或間接貫穿全部的部分,所以咱們通常稱之爲容器
,除此以外,ApplicationContext
還擁有除了bean容器
這種角色外,還包括了獲取整個程序運行的環境參數等信息(好比JDK版本,jre等),其實這部分Spring也作了對應的封裝,稱之爲Enviroment
,下面就跟着小編的eclipse,一塊兒debug下容器的初始化工程吧!學生類Student.java
以下:
package com.wokao666;
public class Student {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(int id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
public Student() {
super();
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
複製代碼
在application.xml
中進行配置,兩個bean
:
<bean id="stu1" class="com.wokao666.Student">
<property name="id" value="1"></property>
<property name="name" value="xiaoming"></property>
<property name="age" value="21"></property>
</bean>
<bean id="stu2" class="com.wokao666.Student">
<property name="id" value="2"></property>
<property name="name" value="xiaowang"></property>
<property name="age" value="22"></property>
</bean>
複製代碼
好了,接下來給最開頭那段代碼打個斷點(Breakpoint
):
急切地加載ContextClosedEvent類,以免在WebLogic 8.1中的應用程序關閉時出現奇怪的類加載器問題。
這一步無需太過在乎!
既然是new ClassPathXmlApplicationContext()
那麼就調用構造器嘛!好,咱們跟着第三步中的super(parent)
,再結合上面第三節的第6小點UML圖一步一步跟蹤,而後咱們來到AbstractApplicationContext
的這個方法:
那麼裏邊的resourcePatternResolver
的類型是什麼呢?屬於第三節說的6大步驟的哪一個部分呢?經過跟蹤能夠看到它的類型是ResourcePatternResolver
類型的,而ResourcePatternResolver
又是繼承了ResourceLoader
接口,所以屬於加載資源模塊,若是還不清晰,我們再看看ResourcePatternResolver
的源碼便可,以下圖:
對吧!不只繼承ResourceLoader
接口,並且只定義一個getResources()
方法用於返回Resource[]
資源集合。再者,這個接口還使用了策略模式
,其具體的實現都在實現類當中,好吧!來看看UML圖就知道了!
PathMatchingResourcePatternResolver
這個實現類呢!它就是用來解釋不一樣路徑資源的,好比你傳入的資源路徑有多是一個常規的url
,又或者有多是以classpath*
前綴,都交給它處理。
ServletContextResourcePatternResolver
這個實現類顧名思義就是用來加載Servlet
上下文的,一般用在web中。
接着第四步的方法,咱們在未進入第四步的方法時,此時會對AbstractApplicationContext
進行實例化,此時this
對象的某些屬性被初始化了(如日誌對象)
,以下圖:
接着進入getResourcePatternResolver()
方法:
第四步說了,PathMatchingResourcePatternResolver
用來處理不一樣的資源路徑的,怎麼處理,咱們先進去看看!
若是找到,此時控制檯會打印找到用於OSGi包URL解析的Equinox FileLocator
日誌。沒打印很明顯找不到!
運行完成返回setParent()
方法。
若是父代是非null
,,則該父代與當前this
應用上下文環境合併。顯然這一步並無作什麼事!parent
顯然是null
的,那麼就不合並嘛!仍是使用當前this
的環境。
作個總結:前六步基本上作了兩件事:
ClassPathXmlApplicationContext
實例resourcePatternResolver
對象,方便第七步的資源解析成Resource
對象第七步又回到剛開始第三步的代碼,由於咱們前面6步已經完成對super(parent)
的追蹤。讓咱們看看setConfigLocation()
方法是怎麼一回事~
/**
* Set the config locations for this application context.//未應用上下文設置資源路徑
* <p>If not set, the implementation may use a default as appropriate.//若是未設置,則實現能夠根據須要使用默認值。
*/
public void setConfigLocations(String... locations) {
if (locations != null) {//非空
Assert.noNullElements(locations, "Config locations must not be null");//斷言保證locations的每一個元素都不爲null
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
this.configLocations[i] = resolvePath(locations[i]).trim();//去空格,很好奇resolvePath作了什麼事情?
}
}
else {
this.configLocations = null;
}
}
複製代碼
進入resolvePath()
方法看看:
/**
* 解析給定的資源路徑,必要時用相應的環境屬性值替換佔位符,應用於資源路徑配置。
* Resolve the given path, replacing placeholders with corresponding
* environment property values if necessary. Applied to config locations.
* @param path the original file path
* @return the resolved file path
* @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)
*/
protected String resolvePath(String path) {
return getEnvironment().resolveRequiredPlaceholders(path);
}
複製代碼
進入getEnvironment()
看看:
/**
* {@inheritDoc}
* <p>If {@code null}, a new environment will be initialized via
* {@link #createEnvironment()}.
*/
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
}
複製代碼
進入createEnvironment()
,方法,咱們看到在這裏建立了一個新的StandardEnviroment
對象,它是Environment
的實現類,表示容器運行的環境,好比JDK環境,Servlet環境,Spring環境等等,每一個環境都有本身的配置數據,如System.getProperties()
、System.getenv()
等能夠拿到JDK環境數據;ServletContext.getInitParameter()
能夠拿到Servlet環境配置數據等等,也就是說Spring抽象了一個Environment
來表示環境配置。
生成的StandardEnviroment
對象並無包含什麼內容,只是一個標準的環境,全部的屬性都是默認值。
總結:對傳入的path
進行路徑解析
這一步是重頭戲
先作個小結:到如今爲止,咱們擁有了如下實例:
如今代碼運行到以下圖的refresh()
方法:
看一下這個方法的內容是什麼?
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 刷新前準備工做,包括設置啓動時間,是否激活標識位,初始化屬性源(property source)配置
prepareRefresh();
// 建立beanFactory(過程是根據xml爲每一個bean生成BeanDefinition並註冊到生成的beanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//準備建立好的beanFactory(給beanFactory設置ClassLoader,設置SpEL表達式解析器,設置類型轉化器【能將xml String類型轉成相應對象】,
//增長內置ApplicationContextAwareProcessor對象,忽略各類Aware對象,註冊各類內置的對帳對象【BeanFactory,ApplicationContext】等,
//註冊AOP相關的一些東西,註冊環境相關的一些bean
prepareBeanFactory(beanFactory);
try {
// 模板方法,爲容器某些子類擴展功能所用(工廠後處理器)這裏能夠參考BeanFactoryPostProcessor接口的postProcessBeanFactory方法
postProcessBeanFactory(beanFactory);
// 調用全部BeanFactoryPostProcessor註冊爲Bean
invokeBeanFactoryPostProcessors(beanFactory);
// 註冊全部實現了BeanPostProcessor接口的Bean
registerBeanPostProcessors(beanFactory);
// 初始化MessageSource,和國際化相關
initMessageSource();
// 初始化容器事件傳播器
initApplicationEventMulticaster();
// 調用容器子類某些特殊Bean的初始化,模板方法
onRefresh();
// 爲事件傳播器註冊監聽器
registerListeners();
// 初始化全部剩餘的bean(普通bean)
finishBeanFactoryInitialization(beanFactory);
// 初始化容器的生命週期事件處理器,併發布容器的生命週期事件
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 銷燬已建立的bean
destroyBeans();
// 重置`active`標誌
cancelRefresh(ex);
throw ex;
}
finally {
//重置一些緩存
resetCommonCaches();
}
}
}
複製代碼
在這裏我想說一下,這個refresh()
方法實際上是一個模板方法,不少方法都讓不一樣的實現類去實現,但該類自己也實現了其中一些方法,而且這些已經實現的方法是不容許子類重寫的,好比:prepareRefresh()
方法。更多模板方法設計模式,可看我以前的文章 談一談我對‘模板方法’設計模式的理解(Template)
先進入prepareRefresh()
方法:
/**
* Prepare this context for refreshing, setting its startup date and
* active flag as well as performing any initialization of property sources.
*/
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();//設置容器啓動時間
this.closed.set(false);//容器關閉標誌,是否關閉?
this.active.set(true);//容器激活標誌,是否激活?
if (logger.isInfoEnabled()) {//運行到這裏,控制檯就會打印當前容器的信息
logger.info("Refreshing " + this);
}
// 空方法,由子類覆蓋實現,初始化容器上下文中的property文件
initPropertySources();
//驗證標記爲必需的全部屬性都可解析,請參閱ConfigurablePropertyResolver#setRequiredProperties
getEnvironment().validateRequiredProperties();
//容許收集早期的ApplicationEvents,一旦多播器可用,便可發佈...
this.earlyApplicationEvents = new LinkedHashSet<ApplicationEvent>();
}
複製代碼
控制檯輸出:
三月 22, 2018 4:21:13 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@96532d6: startup date [Thu Mar 22 16:21:09 CST 2018]; root of context hierarchy
複製代碼
進入obtainFreshBeanFactory()
方法:
/**
* 告訴子類刷新內部bean工廠(子類是指AbstractApplicationContext的子類,咱們使用的是ClassPathXmlApplicationContext)
* Tell the subclass to refresh the internal bean factory.
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();//刷新Bean工廠,若是已經存在Bean工廠,那就關閉並銷燬,再建立一個新的bean工廠
ConfigurableListableBeanFactory beanFactory = getBeanFactory();//獲取新建立的Bean工廠
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);//控制檯打印
}
return beanFactory;
}
複製代碼
進入refreshBeanFactory()
方法:
/**
* 該實現執行該上下文的基礎Bean工廠的實際刷新,關閉之前的Bean工廠(若是有的話)以及爲該上下文的生命週期的下一階段初始化新鮮的Bean工廠。
* This implementation performs an actual refresh of this context's underlying * bean factory, shutting down the previous bean factory (if any) and * initializing a fresh bean factory for the next phase of the context's lifecycle.
*/
@Override
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {//若是已有bean工廠
destroyBeans();//銷燬
closeBeanFactory();//關閉
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();//建立一個新的bean工廠
beanFactory.setSerializationId(getId());//爲序列化目的指定一個id,若是須要,能夠將此BeanFactory今後id反序列化回BeanFactory對象。
//定製容器,設置啓動參數(bean可覆蓋、循環引用),開啓註解自動裝配
customizeBeanFactory(beanFactory);
////將全部BeanDefinition載入beanFactory中,此處依舊是模板方法,具體由子類實現
loadBeanDefinitions(beanFactory);
//beanFactory同步賦值
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
複製代碼
總結:這一步主要的工做就是判斷刷新容器前是否已經有beanfactory存在,若是有,那麼就銷燬舊的beanfactory,那麼就銷燬掉而且建立一個新的beanfactory返回給容器,同時將xml文件的BeanDefinition
註冊到beanfactory中。若是不太清楚能夠回過頭看看咱們的第三節第5點內容
進入第九步的loadBeanDefinitions(beanFactory)
方法中去take a look
:
/**
* 使用XmlBeanDefinitionReader來加載beandefnition,以前說過使用reader機制加載Resource資源變爲BeanDefinition對象
* Loads the bean definitions via an XmlBeanDefinitionReader.
* @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
* @see #initBeanDefinitionReader
* @see #loadBeanDefinitions
*/
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 建立XmlBeanDefinitionReader對象
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 使用當前上下文Enviroment中的Resource配置beanDefinitionReader,由於beanDefinitionReader要將Resource解析成BeanDefinition嘛!
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//初始化這個reader
initBeanDefinitionReader(beanDefinitionReader);
//將beandefinition註冊到工廠中(這一步就是將bean保存到Map中)
loadBeanDefinitions(beanDefinitionReader);
}
複製代碼
控制檯輸出:
三月 22, 2018 5:09:40 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [applicationContext.xml]
複製代碼
進入prepareBeanFactory(beanFactory)
方法:
//設置bean類加載器
//設置Spring語言表達式(SpEL)解析器
//掃描ApplicationContextAware bean
//註冊類加載期類型切面織入(AOP)LoadTimeWeaver
//爲各類加載進入beanFactory的bean配置默認環境
複製代碼
postProcessBeanFactory(beanFactory)
方法:
postProcessBeanFactory
一樣做爲一個模板方法,由子類來提供具體的實現,子類能夠有本身的特殊對BeanDefinition
後處理方法,即子類能夠在這對前面生成的BeanDefinition
,即bean
的元數據再處理。好比修改某個bean
的id/name
屬性、scope
屬性、lazy-init
屬性等。
invokeBeanFactoryPostProcessors(beanFactory)
方法:
該方法調用全部的BeanFactoryPostProcessor
,它是一個接口,實現了此接口的類需重寫postProcessBeanFactory()
這個方法,能夠看出該方法跟第十二步的方法是同樣的,只不過做爲接口,更多的是提供給開發者來對生成的BeanDefinition
作處理,由開發者提供處理邏輯。
其他剩下的方法基本都是像初始化消息處理源
,初始化容器事件
,註冊bean監聽器到事件傳播器上
,最後完成容器刷新。
恭喜我,我終於寫完了,一樣也恭喜你,你也閱讀完了。
我很佩服我本身能花這麼長時間進行總結髮布,之因此要進行總結,那是由於小編仍是贊同好記性不如爛筆頭
的說法。
你不記,你過陣子就會忘記,你若記錄,你過陣子也會忘記!區別在於忘記了,能夠回過頭在很短的時間內進行回憶,查漏補缺,減小學習成本。
再者,我認爲我分析的還不是完美的,缺陷不少,所以我將我寫的全部文章發佈出來和你們探討交流,汕頭大學有校訓說得很是地好,那就是說之知識是用來共享的,由於共享了,知識才能承前啓後。
如今再梳理一下Spring初始化過程:
ClassPathXmlApplicationContext
對象,在獲取resourcePatternResolver
對象將xml
解析成Resource
對象。朋友們,發現毛病,請評論告訴小編,一塊兒交流一塊兒交流!