Spring進階- Spring IOC構建原理(二)IOC初始化流程
本文是 IOC 進階第二篇。
本文的目標就是分析 Spring 如何實現將資源配置(以xml配置為例)通過加載,解析,生成 BeanDefination 并注冊到IoC容器中的。
閱讀源碼初學者應該會很頭大,給你們你做一個心理按摩。
Spring 源碼代碼量大,復雜,追源碼 “迷路” 等問題非常讓人苦惱,不過從以下兩個角度入手學習源碼會輕松很多。
- 熟悉 Spring IOC 啟動的工作原理,做到心中有數,就明白下一步源碼應該看哪里。
- 代碼量大,找不到重點,跟著本文一步一步剖析重點。
源碼本質上就是 java 代碼,無非是初始化構造函數,對象之間的調用等,這些都是 java 語言的基本,不會難,重點要從頂層思維看 IOC 啟動流程為何如此編寫?本文做了大量闡述,幫助你從頂層視角看清 IOC 啟動流程。通過閱讀源碼你會發現** IOC 容器在 Spring 中的技術實現不過就是一個 Java util 包的 Map 集合**。
引言
上文介紹了 IOC 進階之體系結構,回顧核心組件,BeanFactory 、BeanRegistry , 繼承 BeanFactory 的 ApplicationContext , ApplicationContext 是一個很重要的組件,它實現了 **ResourceLoader **便具有加載配置的能力。
另外還有一個 BeanDefinitionReader 組件也很重要,ApplicationContext 會調用它解析 Bean 對象。 BeanDefinitionReader 與 BeanDefinition 之間是依賴關系,不要看名字腦部成繼承或者實現哦~ BeanDefinitionReader 調用 BeanDefinition 創建一個 BeanDefinition 實例。
接下來以這幾個組件開始,介紹 IOC 初始化流程,回顧上文最后一張圖。

IOC 容器工作流程描述
我們從萬惡之源 Application 啟動類開始入手,在本系列文章的第一篇當中,有一個 HelloWorld 的例子中有個main 函數
public static void main(String[] args) {
// create and configure beans
ApplicationContext context =
new ClassPathXmlApplicationContext("daos.xml", "services.xml");
// something...
}
**new ClassPathXmlApplicationContext(configLocations)** 構造函數被調用,做了三件事
- 初始化 BeanFactory 和 資源加載器 ResourceLoader
- 設置配置文件路徑
- refresh() 初始化容器
初始化容器是精彩之處,接著看如何初始化。
- 為容器刷新做準備工作,記錄啟動時間,初始化一些狀態標志。
- **初始化BeanFactory **: 創建并初始化
**BeanFactory**,這是容器的核心。 - **準備 BeanFactory: **為
**BeanFactory**設置一些標準的、通用的特性。比如:設置**BeanFactory**的類加載器。 - **BeanFactory 后處理 : **提供一個擴展點,允許子類在
**BeanFactory**標準化準備完成后,對其進行個性化的定制 - **調用 BeanFactory 后處理器: **在所有 Bean 實例化之前,對
**BeanFactory**的元數據進行修改。 - **注冊 Bean 后處理器: **將所有
**BeanPostProcessor**注冊到**BeanFactory**中。 - **初始化消息源: **為容器提供國際化(i18n)支持。
- **初始化事件廣播器:**為容器提供事件發布機制。
- ** 刷新onRefresh() : **留給子類在實例化 Bean 之前初始化一些特殊的、與上下文相關的資源。
- **注冊監聽器:**將所有事件監聽器注冊到事件廣播器中。
- **完成 BeanFactory 初始化: **實例化所有剩余的、非懶加載的單例 Bean。這是 IoC 容器最核心、最耗時的步驟。
- **完成刷新:**完成整個刷新過程,發布容器啟動完成事件。
這是從源碼的角度解釋完整的 IOC 初始化流程,在本文目錄的 4、初始化的主題流程中可看見源碼。后面的源碼剖析,是基于此流程選核心重點進行深入剖析,如:XML 加載 -> 轉成 Document 對象 -> 轉成 BeanDefinition 對象 -> 封裝 BeanDefinitionHolder -> 注冊 BeanDefinitionHolder 到容器中。
IOC 容器的工作原理
以 XML 為例,從 Spring 源碼的角度探討 XML 配置是如何加載到 IOC 容器中。
1、初始化的入口
在本系列文章的第一篇當中,有一個 HelloWorld 的例子演示了如何使用 XML 啟動項目,其中 Spring 啟動入口 main 函數關鍵代碼是:
public static void main(String[] args) {
// create and configure beans
ApplicationContext context =
new ClassPathXmlApplicationContext("daos.xml", "services.xml");
// something...
}
ClassPathXmlApplicationContext 類的構造函數如下:
public ClassPathXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, (ApplicationContext)null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
// 設置Bean資源加載器
super(parent);
// 設置配置路徑
this.setConfigLocations(configLocations);
// 最終目標:IOC 容器刷新的入口 , 真正的精彩!
if (refresh) {
this.refresh();
}
}
super(parent); 重點關注, 接下來深入挖掘它,它的作用之一是為 IOC 容器設置 Bean 資源加載器。
2、設置資源解析器和環境
挖掘 **super(parent) , **調用父類容器 AbstractApplicationContext 的構造方法為容器設置好 Bean 資源加載器,代碼如下:
public AbstractApplicationContext(@Nullable ApplicationContext parent) {
// 默認構造函數初始化容器id, name, 狀態 以及 資源解析器
this();
// 將父容器的Environment合并到當前容器
this.setParent(parent);
}
重點這兩個 this.
**1) ** this(); 通過 AbstractApplicationContext 默認構造函數初始化容器id, name, 狀態 以及 資源解析器。
public AbstractApplicationContext() {
this.logger = LogFactory.getLog(this.getClass());
this.id = ObjectUtils.identityToString(this);
this.displayName = ObjectUtils.identityToString(this);
this.beanFactoryPostProcessors = new ArrayList();
this.active = new AtomicBoolean();
this.closed = new AtomicBoolean();
this.startupShutdownMonitor = new Object();
this.applicationStartup = ApplicationStartup.DEFAULT;
this.applicationListeners = new LinkedHashSet();
this.resourcePatternResolver = this.getResourcePatternResolver();
}
// Spring資源加載器
protected ResourcePatternResolver getResourcePatternResolver() {
return new PathMatchingResourcePatternResolver(this);
}
2) this.setParent(parent); 方法將父容器的 Environment 合并到當前容器。 上文我們有簡單聊過父子容器,概念不難,理解成有多個容器, IOC 容器也有繼承關系,父容器定義全局的、通用的 Bean。比如,數據庫連接池、通用的安全服務、系統級別的配置等。子容器定義自己領域的Bean 。比如:各種 Service Bean 。
public void setParent(@Nullable ApplicationContext parent) {
this.parent = parent;
if (parent != null) { // 目前 if 條件不成立
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment) {
this.getEnvironment().merge((ConfigurableEnvironment)parentEnvironment);
}
}
}
@Nullable 注解是可以為空,那么這里 parent 容器初始化自然是空的。
3、設置配置路徑
現在目光回看** 1、初始化入口**,里面的 ClassPathXmlApplicationContext 的 super(parent) 加載資源搞定,繼續 this.setConfigLocations(configLocations) , 看源碼:
public void setConfigLocations(@Nullable String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for(int i = 0; i < locations.length; ++i) {
// 解析配置路徑
this.configLocations[i] = this.resolvePath(locations[i]).trim();
}
} else {
this.configLocations = null;
}
}
protected String resolvePath(String path) {
// 從上一步Environment中解析
return this.getEnvironment().resolveRequiredPlaceholders(path);
}
setConfigLocations 方法通過調用其父類 AbstractRefreshableConfigApplicationContext 的方法進行對 Bean定義資源文件的定位。
進入到 ClassPathXmlApplicationContext 的初始化流程了。
4、初始化的主體流程
Spring IoC容器對Bean定義資源的載入是從refresh()函數開始的,refresh()是一個模板方法,refresh()方法的作用是:在創建IoC容器前,如果已經有容器存在,則需要把已有的容器銷毀和關閉,以保證在refresh之后使用的是新建立起來的IoC容器。refresh的作用類似于對IoC容器的重啟。
@Override
public void refresh() throws BeansException, IllegalStateException {
// 加鎖,防止并發刷新或關閉
synchronized (this.startupShutdownMonitor) {
// 1. 準備工作:設置啟動時間、校驗配置文件等
prepareRefresh();
// 2. 獲取Bean工廠:創建并加載Bean定義到BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 準備Bean工廠:設置工廠的標準特性,如類加載器、后處理器等
prepareBeanFactory(beanFactory);
try {
// 4. 后處理Bean工廠:提供給子類的擴展點,可對BeanFactory進行自定義
postProcessBeanFactory(beanFactory);
// 5. 調用工廠后處理器:執行BeanFactoryPostProcessor,修改Bean的定義信息
invokeBeanFactoryPostProcessors(beanFactory);
// 6. 注冊Bean后處理器:注冊BeanPostProcessor,用于攔截Bean的創建過程
registerBeanPostProcessors(beanFactory);
// 7. 初始化消息源:用于國際化(i18n)
initMessageSource();
// 8. 初始化事件廣播器:用于發布事件
initApplicationEventMulticaster();
// 9. 刷新:提供給子類的擴展點,用于初始化特殊的Bean
onRefresh();
// 10. 注冊監聽器:將所有監聽器Bean注冊到事件廣播器
registerListeners();
// 11. 實例化Bean:實例化所有剩余的非懶加載單例Bean
finishBeanFactoryInitialization(beanFactory);
// 12. 完成刷新:發布刷新完成事件,容器啟動完畢
finishRefresh();
}
catch (BeansException ex) {
// 發生異常時,銷毀已創建的Bean,并重置容器狀態
destroyBeans();
cancelRefresh(ex);
throw ex;
}
finally {
// 重置Spring核心的公共緩存
resetCommonCaches();
}
}
}
這個設計便是本文的主題,非常典型的資源類加載處理型的思路,頭腦中要形成如下圖的頂層思路,而不是只停留在流水式的方法上面。

不要將這段代碼看成流水線一樣的設計,如果上圖你還是無法從頂層思路看待 IOC 容器初始化,那么下面將對頂層思路的四個重點進行分析,必然能幫到你。
1)模板方法設計模式,模板方法中使用典型的鉤子方法。
**refresh()** 方法本身就是一個模板方法。它定義了 Spring 容器啟動的標準、不可變的流程骨架。
這個骨架就像一個固定的生產線: **準備 -> 加載資源 -> 配置 -> 初始化核心組件 -> 實例化 -> 完成**
Spring 框架的設計者規定,任何一個 **ApplicationContext** 的實現(無論是基于 XML、注解還是配置類)都必須遵循這個骨架。這保證了所有 Spring 應用啟動行為的一致性。
2)將具體的初始化加載方法插入到鉤子方法之間
鉤子方法是啥?
模板方法魚總帶我們在項目中寫過,都不陌生吧,抽象類定義一套固定模板,并且部分的方法是抽象的,由子類按需實現,父類固定流程,子類按需變化。鉤子方法便是父類的抽象方法。
鉤子方法體現為**空實現或留給子類覆蓋的方法,**其中 postProcessBeanFactory(beanFactory); 是一個典型的鉤子,**AbstractApplicationContext** 不知道子類(如 **AnnotationConfigWebApplicationContext**)需要對 **BeanFactory** 做什么特殊定制。它就在這里“挖個坑”,讓子類來“填坑”。子類可以覆蓋此方法,添加自己特有的 **BeanPostProcessor** 或進行其他定制。
3)將初始化的階段封裝,讓初始化過程有狀態
這個設計讓初始化過程不再是一個黑盒,而是一個可以被觀察和記錄的、分階段的狀態機。
看兩行代碼:
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
...
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
...
beanPostProcess.end();
...
contextRefresh.end();
**applicationStartup.start("...")**:這就像在流水線的每個關鍵工位前打卡,聲明“現在進入了【XX階段】”。**step.end()**:在階段完成后打卡下班,聲明“【XX階段】已完成”。
雖然就是一個普通的記錄功能,類似于日志沒啥技術含量,但它的設計精髓是:
- 可觀測:使用監控工具清晰地看到容器啟動卡在了哪個階段,每個階段耗時多久,極大地提升了問題排查的效率。
- 結構清晰:代碼本身就是一份活的文檔,明確地劃分了“上下文刷新”、“Bean后置處理”等邏輯階段,讓閱讀者能快速把握代碼結構。
4) 健壯性設計:**try/catch/finally** 的藝術
資源加載和初始化過程極易失敗(如配置文件找不到、類定義錯誤、數據庫連不上等)。一個不健壯的設計可能導致容器處于一個半死不活的中間狀態,資源泄漏。
這里的 try/catch/finally 是運用之模范,魚總帶我們項目時也經常寫,我們感受一下 Spring 小組和魚總的 try/catch/finally 有多少相似之處。
**try 塊:**包裹了所有核心的初始化步驟。這些步驟是原子性的——要么全部成功,要么全部失敗。保證容器從一個初始狀態,平滑過渡到一個完全就緒可用的狀態。
**catch 塊:**具有失敗回滾的作用,非常重要,一旦try 的任何一步發生異常,它會立即執行清理和銷毀操作,將容器內部狀態標記為 “已取消”、“非活躍”,告訴外界這次啟動失敗,將異常拋出,通知啟動的調用者發生了錯誤。
**finally 塊:**無論 try 是成功還是失敗,必定重置 Spring 核心的公共緩存
接下來從初始化的代碼開始詳解源碼,上高速,高速入口代碼:
// 2. 獲取Bean工廠:創建并加載Bean定義到BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
初始化BeanFactory之obtainFreshBeanFactory
AbstractApplicationContext的obtainFreshBeanFactory()方法調用子類容器的refreshBeanFactory()方法,啟動容器載入Bean定義資源文件的過程,代碼如下
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 這里使用了委派設計模式,父類定義了抽象的refreshBeanFactory()方法,具體實現調用子類容器的refreshBeanFactory()方法
refreshBeanFactory();
return getBeanFactory();
}
AbstractApplicationContext類中只抽象定義了refreshBeanFactory()方法,容器真正調用的是其子類AbstractRefreshableApplicationContext實現的refreshBeanFactory()方法; 在創建IoC容器前,如果已經有容器存在,則需要把已有的容器銷毀和關閉,以保證在refresh之后使用的是新建立起來的IoC容器。方法的源碼如下:
protected final void refreshBeanFactory() throws BeansException {
// 如果已經有容器存在,則需要把已有的容器銷毀和關閉,以保證在refresh之后使用的是新建立起來的IoC容器
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 創建DefaultListableBeanFactory,并調用loadBeanDefinitions(beanFactory)裝載bean定義
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory); // 對IoC容器進行定制化,如設置啟動參數,開啟注解的自動裝配等
loadBeanDefinitions(beanFactory); // 調用載入Bean定義的方法,主要這里又使用了一個委派模式,在當前類中只定義了抽象的loadBeanDefinitions方法,具體的實現調用子類容器
this.beanFactory = beanFactory;
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
初始化BeanFactory之loadBeanDefinitions
這一步加載 xml 文件啦,如: {“daos.xml”, “services.xml”}
AbstractRefreshableApplicationContext中只定義了抽象的loadBeanDefinitions方法,容器真正調用的是其子類AbstractXmlApplicationContext對該方法的實現,AbstractXmlApplicationContext的主要源碼如下:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 創建XmlBeanDefinitionReader,即創建Bean讀取器,并通過回調設置到容器中去,容器使用該讀取器讀取Bean定義資源
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// 配置上下文的環境,資源加載器、解析器
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // 為Bean讀取器設置SAX xml解析器
// 允許子類自行初始化(比如校驗機制),并提供真正的加載方法
initBeanDefinitionReader(beanDefinitionReader); // 當Bean讀取器讀取Bean定義的Xml資源文件時,啟用Xml的校驗機制
loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// 加載XML配置方式里的Bean定義的資源
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
// 加載構造函數里配置的Bean配置文件,即{"aspects.xml", "daos.xml", "services.xml"}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
可以看到在這里創建了一個 XmlBeanDefinitionReader 對象,它實現了 AbstractBeanDefinitionReader 、BeanDefinitionReader ,看一下 AbstractBeanDefinitionReader 可以了解讀取 XML 的過程。
AbstractBeanDefinitionReader讀取Bean定義資源
AbstractBeanDefinitionReader的loadBeanDefinitions方法源碼如下:
這個類并不是加載 xml 的過程,它是創建了一個 Resource 對象,分派 Resource 對象給子類來加載 Bean.
@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
}
// 模式匹配類型的解析器,這種方式是加載多個滿足匹配條件的資源
if (resourceLoader instanceof ResourcePatternResolver) {
try {
// 獲取到要加載的資源
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int count = loadBeanDefinitions(resources); // 委派調用其子類XmlBeanDefinitionReader的方法,實現加載功能
if (actualResources != null) {
Collections.addAll(actualResources, resources);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
}
return count;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// 只能通過絕對路徑URL加載單個資源.
Resource resource = resourceLoader.getResource(location);
int count = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isTraceEnabled()) {
logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
}
return count;
}
}
從對AbstractBeanDefinitionReader的loadBeanDefinitions方法源碼分析可以看出該方法做了以下兩件事:
- 首先,調用資源加載器的獲取資源方法resourceLoader.getResource(location),獲取到要加載的資源。
- 其次,真正執行加載功能是其子類XmlBeanDefinitionReader的loadBeanDefinitions方法。
XmlBeanDefinitionReader加載Bean定義資源
繼續看子類XmlBeanDefinitionReader的loadBeanDefinitions(Resource …)方法看到代表bean文件的資源定義以后的載入過程。
通過源碼分析,載入Bean定義資源文件的最后一步是將Bean定義資源轉換為Document對象,該過程由documentLoader實現, documentLoader 內部真正進行 IO 讀取。
/**
* 本質上是加載XML配置的Bean。
* @param inputSource the SAX InputSource to read from
* @param resource the resource descriptor for the XML file
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// doLoadDocument 方法內部是真正進行 IO 讀取 XML
Document doc = doLoadDocument(inputSource, resource); // 將Bean定義資源轉換成Document對象
int count = registerBeanDefinitions(doc, resource); // 對 Bean 定義的解析過程
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
// 使用配置的DocumentLoader加載XML定義文件為Document.
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
DocumentLoader將Bean定義資源轉換為Document對象
這一步是將 XML 文件的符號轉換成 Document 對象,這一步的內部有兩步,一解析 XML, 二解析 Document , 然后才是轉換。
DocumentLoader **將 Bean定義資源 **轉換成 **Document對象 **的源碼如下:
// 使用標準的JAXP將載入的Bean定義資源轉換成document對象
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
// 創建文件解析器工廠
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
// 創建文檔解析器
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource); // 解析
}
protected DocumentBuilderFactory createDocumentBuilderFactory(int validationMode, boolean namespaceAware)
throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(namespaceAware);
// 設置解析XML的校驗
if (validationMode != XmlValidationModeDetector.VALIDATION_NONE) {
factory.setValidating(true);
if (validationMode == XmlValidationModeDetector.VALIDATION_XSD) {
// Enforce namespace aware for XSD...
factory.setNamespaceAware(true);
try {
factory.setAttribute(SCHEMA_LANGUAGE_ATTRIBUTE, XSD_SCHEMA_LANGUAGE);
}
catch (IllegalArgumentException ex) {
ParserConfigurationException pcex = new ParserConfigurationException(
"Unable to validate using XSD: Your JAXP provider [" + factory +
"] does not support XML Schema. Are you running on Java 1.4 with Apache Crimson? " +
"Upgrade to Apache Xerces (or Java 1.5) for full XSD support.");
pcex.initCause(ex);
throw pcex;
}
}
}
return factory;
}
該解析過程調用JavaEE標準的JAXP標準進行處理。
至此Spring IoC容器根據定位的Bean定義資源文件,將其加載讀入并轉換成為Document對象過程完成。
接下來我們要繼續分析Spring IoC容器將載入的Bean定義資源文件轉換為Document對象之后,是如何將其解析為Spring IoC管理的Bean對象并將其注冊到容器中的。
XmlBeanDefinitionReader解析載入的 Bean 定義資源文件
這一步是對 XML 文件解析。
XmlBeanDefinitionReader 類中的 doLoadBeanDefinitions 方法是從特定XML文件中實際載入Bean定義資源的方法,該方法在載入Bean定義資源之后將其轉換為Document對象,接下來調用registerBeanDefinitions 啟動Spring IoC容器對Bean定義的解析過程,registerBeanDefinitions方法源碼如下:
// 按照Spring的Bean語義要求將Bean定義資源解析并轉換為容器內部數據結構
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 解析過程入口,這里使用了委派模式,具體的解析實現過程有實現類DefaultBeanDefinitionDocumentReader完成
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore; // 返回此次解析了多少個對象
}
// 創建BeanDefinitionDocumentReader對象,解析Document對象
protected BeanDefinitionDocumentReader createBeanDefinitionDocumentReader() {
return BeanUtils.instantiateClass(this.documentReaderClass);
}
/**
* Create the {@link XmlReaderContext} to pass over to the document reader.
*/
public XmlReaderContext createReaderContext(Resource resource) {
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
Bean定義資源的載入解析分為以下兩個過程:
- 首先,通過調用XML解析器將Bean定義資源文件轉換得到Document對象,但是這些Document對象并沒有按照Spring的Bean規則進行解析。這一步是載入的過程
- 其次,在完成通用的XML解析之后,按照Spring的Bean規則對Document對象進行解析。
Document對象解析的過程是在接口BeanDefinitionDocumentReader的實現類DefaultBeanDefinitionDocumentReader 中實現的。
DefaultBeanDefinitionDocumentReader對Bean定義的Document對象解析
對 Document 對象進行解析,轉換成 Document 對象。
BeanDefinitionDocumentReader接口通過registerBeanDefinitions方法調用其實現類DefaultBeanDefinitionDocumentReader對Document對象進行解析,解析的代碼如下:
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}
// 注冊<beans/>配置的Beans
@SuppressWarnings("deprecation") // for Environment.acceptsProfiles(String...)
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// We cannot use Profiles.of(...) since profile expressions are not supported
// in XML config. See SPR-12458 for details.
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate); // 從Document的根元素開始進行Bean定義的Document對象
postProcessXml(root);
this.delegate = parent;
}
BeanDefinitionParserDelegate解析Bean定義資源文件生成BeanDefinition
將 Ducument 對象轉換為 BeanDefinition 對象。
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
* @param root the DOM root element of the document
*/
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 如果元素節點是<Import>導入元素,進行導入解析
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
// 如果元素節點是<Alias>別名元素,進行別名解析
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
// 如果元素節點<Bean>元素, 按照Spring的Bean規則解析元素
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
// 如果元素節點<Beans>元素,即它是嵌套類型的
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// 遞歸解析
doRegisterBeanDefinitions(ele);
}
}
然后是把 BeanDefinition 封裝成 BeanDefinitionHolder 的源碼。BeanDefinitionHolder 是一個信息更豐富的“工作載體”
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 注冊最終的裝飾實例
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event.
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
得到一個 BeanDefinitionHolder 對象就代表 Bean 已經正式解析好了,可以被注入到容器。
解析過后的BeanDefinition在IoC容器中的注冊
這一步是要把 BeanDefinitionHolder 對象注冊到容器中。
調用BeanDefinitionReaderUtils的registerBeanDefinition方法向IoC容器注冊解析的Bean,BeanDefinitionReaderUtils的注冊的源碼如下:
// 通過BeanDefinitionRegistry將BeanDefinitionHolder注冊到BeanFactory
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
調用BeanDefinitionReaderUtils向IoC容器注冊解析的BeanDefinition時,真正完成注冊功能的是DefaultListableBeanFactory。
DefaultListableBeanFactory向IoC容器注冊解析后的BeanDefinition
這里能看到 Spirng IOC 容器技術實現的真面目,本質上是一個 Map<String, BeanDefinition> .
IOC 容器本質上就是一個 beanDefinitionMap, 注冊即將BeanDefinition put到map中
/** Map of bean definition objects, keyed by bean name. */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
/** Map from bean name to merged BeanDefinitionHolder. */
private final Map<String, BeanDefinitionHolder> mergedBeanDefinitionHolders = new ConcurrentHashMap<>(256);
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
// 如果已經注冊
if (existingDefinition != null) {
// 檢查是否可以覆蓋
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isInfoEnabled()) {
logger.info("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
// 覆蓋
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
//重置所有已經注冊過的BeanDefinition的緩存
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
else if (isConfigurationFrozen()) {
clearByTypeCache();
}
}
附上一張 IOC 啟動流程圖

參考文章
https://pdai.tech/md/spring/spring-x-framework-ioc-source-2.html
https://blog.csdn.net/qq_36212439/article/details/82749963
https://juejin.cn/post/6973884466171215908
https://juejin.cn/post/6844903838743265294
https://blog.csdn.net/hjing123/article/details/104867343
http://www.rzrgm.cn/wl20200316/p/12522993.html

浙公網安備 33010602011771號