Spring源碼分析之-載入IOC容器
- 2019 年 10 月 3 日
- 筆記
本文接上一篇文章 SpringIOC 源碼,控制反轉前的處理(https://mp.weixin.qq.com/s/9RbVP2ZQVx9-vKngqndW1w) 繼續進行下面的分析
首先貼出 Spring bean容器的刷新的核心 11個步驟進行祭拜(一定要讓我學會了...阿門)
// 完成IoC容器的創建及初始化工作 @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // 1: 刷新前的準備工作。 prepareRefresh(); // 告訴子類刷新內部bean 工廠。 // 2:創建IoC容器(DefaultListableBeanFactory),載入解析XML文件(最終存儲到Document對象中) // 讀取Document對象,並完成BeanDefinition的載入和註冊工作 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // 3: 對IoC容器進行一些預處理(設置一些公共屬性) prepareBeanFactory(beanFactory); try { // 4: 允許在上下文子類中對bean工廠進行後處理。 postProcessBeanFactory(beanFactory); // 5: 調用BeanFactoryPostProcessor後置處理器對BeanDefinition處理 invokeBeanFactoryPostProcessors(beanFactory); // 6: 註冊BeanPostProcessor後置處理器 registerBeanPostProcessors(beanFactory); // 7: 初始化一些消息源(比如處理國際化的i18n等消息源) initMessageSource(); // 8: 初始化應用事件多播器 initApplicationEventMulticaster(); // 9: 初始化一些特殊的bean onRefresh(); // 10: 註冊一些監聽器 registerListeners(); // 11: 實例化剩餘的單例bean(非懶載入方式) // 注意事項:Bean的IoC、DI和AOP都是發生在此步驟 finishBeanFactoryInitialization(beanFactory); // 12: 完成刷新時,需要發布對應的事件 finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // 銷毀已經創建的單例避免佔用資源 destroyBeans(); // 重置'active' 標籤。 cancelRefresh(ex); // 傳播異常給調用者 throw ex; } finally { // 重置Spring核心中的常見內省快取,因為我們可能不再需要單例bean的元數據了... resetCommonCaches(); } } } 下面來分析上述流程中的第二個步驟: 創建並解析IOC容器
我們開始進入分析,首先會調用到 AbstractApplicationContext 中的 obtainFreshBeanFactory 方法,此方法會進行IOC容器的刷新並取出BeanFactory,程式碼如下
// 告訴內部子類刷新內部的bean factory protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { // 主要是通過該方法完成IoC容器的刷新 // ClassPathXmlApplicationContext.refreshBeanFactory 調用的是 AbstractRefreshApplicationContext // AnnotationConfigApplicationContext.refreshBeanFactory調用的是GenericApplicationContext refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); ... return beanFactory; } - 首先第一步,
refreshBeanFactory,刷新Bean工廠,refreshBeanFactory是一個介面,此介面在 AbstractApplicationContext 中進行定義,先看一下這個JavaDoc對這個介面的定義
/** * 子類必須實現這個方法才能執行實際的配置載入。此方法在任何其他初始化工作開始之前由refresh()方法調用 * 一個子類創建了一個新的 bean factory 並且持有對它的引用,或者返回它擁有的單個BeanFactory實例。 * 在後一種情況下,如果多次刷新上下文,它通常會拋出 IllegalStateException。 */ protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException; 
它有兩個實現類,默認的是 AbstractRefreshableApplicationContext 類,它的refreshBeanFactory方法如下
@Override protected final void refreshBeanFactory() throws BeansException { // 如果之前有IoC容器,則銷毀 if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { // 創建IoC容器,也就是 DefaultListableBeanFactory, 初始化AbstractBeanFactory // 註冊BeanNameAware,BeanClassLoaderAware,BeanFactoryAware, 設置當前BeanFactory DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); // 設置工廠的屬性:是否允許BeanDefinition覆蓋和是否允許循環依賴 customizeBeanFactory(beanFactory); // 調用載入BeanDefinition的方法,在當前類中只定義了抽象的loadBeanDefinitions方法,具體的實現調用子類容器 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } } 如果之前有IOC容器的話,就進行銷毀,並且關閉容器。適用於第二次調用ClassPathXmlApplicationContext("配置文件"),由於是第一次載入配置文件,還未創建BeanFactory,所以hasBeanFactory()返回false。
下面這一步就是創建IOC容器,也就是DefaultListableBeanFactory
// 為上下文創建一個內部工廠,默認的實現創建了一個內部的DefaultListableBeanFactory.getInternalParentBeanFactory(), // 在子類中被重寫,例如自定義DefaultListableBeanFactory的設置。 protected DefaultListableBeanFactory createBeanFactory() { return new DefaultListableBeanFactory(getInternalParentBeanFactory()); } 上述方法傳遞的是一個getInternalParentBeanFactory()方法,我們先來看一下這個方法:
protected BeanFactory getInternalParentBeanFactory() { return (getParent() instanceof ConfigurableApplicationContext) ? ((ConfigurableApplicationContext) getParent()).getBeanFactory() : getParent(); } getParent()這個方法會返回父類上下文,如果沒有(父類)的話,返回null(也就表明這個上下文是繼承樹的根節點。)這句程式碼的意思也就是說如果它實現了 ConfigurableApplicationContext,則返回父類上下文的BeanFactory,如果沒有的話,就返回父上下文本身。因為沒有設置過父節點上下文,所以此時的getInternalParentBeanFactory()方法返回為null。
下一步返回一個DefaultListableBeanFactory,首先來看一下DefaultListableBeanFactory的繼承體系

它的調用流程如下:
- 在
AbstractRefreshableApplicationContext類中返回一個DefaultListableBeanFactory的構造函數 DefaultListableBeanFactory構造函數中調用AbstractAutowireCapableBeanFactory的構造函數AbstractAutowireCapableBeanFactory帶參數的構造函數會調用AbstractAutowireCapableBeanFactory無參數的構造函數,並註冊了BeanNameAware、BeanFactoryAware、BeanClassLoaderAware介面。並設置setParentBeanFactory(parentBeanFactory) 為null(parentBeanFactory就是上面getInternalParentBeanFactory()方法的返回值);

AbstractAutowireCapableBeanFactory會調用父類AbstractBeanFactory無參構造方法並初始化。
此時的DefaultListableBeanFactory會帶上一系列的初始化欄位。Bean工廠說的也就是DefaultListableBeanFactory
- 在createBeanFactory()之後,會給ClassPathXmlApplicationContext設置全局唯一Id
beanFactory.setSerializationId(getId()); - 第三步,設置
DefaultListableBeanFactory的屬性,是否允許重寫Bean的定義資訊和是否允許循環依賴。沒有設置,默認為null
/* * 通過上下文自定義內部bean工廠。 嘗試調用每一個refresh 方法。 * 默認實現應用此上下文setAllowBeanDefinitionOverriding 是否允許Bean定義重寫 * 和 setAllowCircularReferences 是否允許循環依賴設置。 * 如果特殊情況,可以在子類中重寫以任何自定義DefaultListableBeanFactory 設置。 */ protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { if (this.allowBeanDefinitionOverriding != null) { beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } if (this.allowCircularReferences != null) { beanFactory.setAllowCircularReferences(this.allowCircularReferences); } } - 最關鍵的一步,載入Bean的定義資訊。所有關於Bean Definitions都會在這一步的這個方法進行解析。調用載入BeanDefinition的方法,在當前類中只定義了抽象的
loadBeanDefinitions方法,具體的實現調用子類容器AbstractXmlApplicationContext中的loadBeanDefinitions方法,具體程式碼如下
// 通過XmlBeanDefinitionReader 載入bean 定義資訊。 @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { // 創建一個BeanDefinition閱讀器,通過閱讀XML文件,真正完成BeanDefinition的載入和註冊 XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); // 配置bean 定義閱讀器和上下文資源載入環境。 beanDefinitionReader.setEnvironment(this.getEnvironment()); beanDefinitionReader.setResourceLoader(this); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this)); // 允許子類提供自定義初始化的reader,然後繼續載入bean定義資訊。 initBeanDefinitionReader(beanDefinitionReader); // 委託給BeanDefinition閱讀器去載入BeanDefinition loadBeanDefinitions(beanDefinitionReader); } - 首先來看第一步,創建XmlBeanDefinitionReader並進行BeanDefinition的載入和註冊。初始化XmlBeanDefinitionReader(beanFactory)構造方法,然後調用AbstractBeanDefinitionReader(BeanDefinitionRegistry registry)的構造方法,因為
DefaultListableBeanFactory實現了BeanDefinitionRegistry介面,所以就把DefaultListableBeanFactory當作一個BeanDefinitionRegistry傳遞進去。下面看程式碼
/* 通過bean 工廠創建一個新的 AbstractBeanDefinitionReader。 如果傳入的bean 工廠不僅實現了BeanDefinitionRegistry 介面還實現了ResourceLoader 介面。 將會使用默認的ResourceLoader。 這是 ApplicationContext 實現的情況。 如果給定一個普通的BeanDefinitionRegistry,則默認的 ResourceLoader 會是PathMatchingResourcePatternResolver 如果傳入的bean 工廠實現了 EnvironmentCapable,則此讀取器將使用此環境。否則,這個讀取器 將會初始化並且使用 StandardEnvironment。 所有ApplicationContext的實現都是 EnvironmentCapable, 然而通常BeanFactory 的實現則不是。 */ protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) { ... if (this.registry instanceof ResourceLoader) { this.resourceLoader = (ResourceLoader) this.registry; } else { this.resourceLoader = new PathMatchingResourcePatternResolver(); } if (this.registry instanceof EnvironmentCapable) { this.environment = ((EnvironmentCapable) this.registry).getEnvironment(); } else { this.environment = new StandardEnvironment(); } } 上面程式碼的意思就是,如果BeanDefinitionRegistry實現了ResourceLoader介面,就會強制轉換為ResourceLoader,否則,將會new 一個PathMatchingResourcePatternResolver(),如果實現了EnvironmentCapable介面,就直接獲取系統環境,否則就直接new一個StandardEnvironment。
- 完成第一步之後,然後配置bean閱讀器和上下文資源載入環境,允許子類提供自定義初始化的reader,然後繼續載入bean定義資訊。這一步希望子類實現自定義的bean載入資訊。
- 最後一步進行真正意義上當bean載入,委託給BeanDefinition閱讀器去載入BeanDefinition,來看具體的解析過程
// 委託給XmlBeanDefinition閱讀器去載入BeanDefinition // loadBeanDefinitions(beanDefinitionReader); ↓ @Override public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { // 將讀入的XML資源進行特殊編碼處理 return loadBeanDefinitions(new EncodedResource(resource)); } // loadBeanDefinitions ↓ // loadBeanDefinitions(resources) 經過一系列的調用最終會調用到下面的程式碼 // XmlBeanDefinitionReader 部分程式碼省略 public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { ... // 從InputStream中得到XML的解析源 InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } // 這裡是具體的讀取過程 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } // doLoadBeanDefinitions ↓ protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { // 通過DOM4J載入解析XML文件,最終形成Document對象 Document doc = doLoadDocument(inputSource, resource); // 通過對Document對象的操作,完成BeanDefinition的載入和註冊工作 return registerBeanDefinitions(doc, resource); } // registerBeanDefinitions ↓ public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { // 創建BeanDefinitionDocumentReader來解析Document對象,完成BeanDefinition解析 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); // 獲得容器中已經註冊的BeanDefinition數量 int countBefore = getRegistry().getBeanDefinitionCount(); // 解析過程入口,BeanDefinitionDocumentReader只是個介面,具體的實現過程在DefaultBeanDefinitionDocumentReader完成 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // 統計新的的BeanDefinition數量 return getRegistry().getBeanDefinitionCount() - countBefore; } 到這一步仍舊只是創建了各種需要解析的閱讀器對象,文檔解析對象,解析之前的準備工作,
這裡說一下BeanDefinitionDocumentReader 這個介面:
/* * 每個要解析的文檔實例化:實現可以在執行 * registerBeanDefinitions 方法時保存實例變數中的狀態 * 例如,為文檔中的所有bean定義定義的全局設置。 */ public interface BeanDefinitionDocumentReader { /* * 從 DOM 文檔中讀取Bean 定義資訊並且通過閱讀器上下文環境把它們註冊進registry中 */ void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) throws BeanDefinitionStoreException; } 它的默認實現類只有一個,就是 DefaultBeanDefinitionDocumentReader ,此類負責做真正的bean 解析工作。
下面registerBeanDefinitions之後的方法會完成真正的bean解析工作:
DefaultBeanDefinitionDocumentReader.java
// 這個實現用來解析 spring-beans 約束 // 打開DOM 文檔,初始化默認<beans/>的設置;然後解析其中的bean定義資訊。 @Override public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; logger.debug("Loading bean definitions"); // 獲得Document的根元素<beans>標籤 Element root = doc.getDocumentElement(); // 真正實現BeanDefinition解析和註冊工作 doRegisterBeanDefinitions(root); } // doRegisterBeanDefinitions(root); ↓ protected void doRegisterBeanDefinitions(Element root) { /* 任何嵌套的<beans>元素都將導致此方法中的遞歸。 為了正確傳播和保留<beans>default-* 屬性,跟蹤當前的 父委託,可能為null。 創建新的(子)委託,引用父項以進行回退,然後最終將this.delegate重置為其原始(父)引用。 此行為模擬了一堆代理,而實際上並不需要一個代理。 */ // 這裡使用了委託模式,將具體的BeanDefinition解析工作交給了BeanDefinitionParserDelegate去完成 BeanDefinitionParserDelegate parent = this.delegate; this.delegate = createDelegate(getReaderContext(), root, parent); // 判斷該根標籤是否包含http://www.springframework.org/schema/beans默認命名空間 ... // 在解析Bean定義之前,進行自定義的解析,增強解析過程的可擴展性 preProcessXml(root); // 委託給BeanDefinitionParserDelegate,從Document的根元素開始進行BeanDefinition的解析 parseBeanDefinitions(root, this.delegate); // 在解析Bean定義之後,進行自定義的解析,增加解析過程的可擴展性 postProcessXml(root); this.delegate = parent; } 看到這裡已經真心不容易了,需要休息一下嗎?怎麼說呢,源碼這個東西是需要耐住性子並且需要你投入大量精力的,看過一遍沒有印象太正常了,並且看源碼的前提是沒人打擾,需要一個極度安靜的環境。
tips: 戴上耳機會好很多
在回到正題之前,我先來問你一個問題,你知道代理模式和委託模式的區別嗎?
我是這樣理解的
委託模式是自己不做這件事情呢,而是把事情交給別人去做
代理模式是讓別人搭把手,而自己是真正做這件事情的主角,因為代理實現類(實現了InvocationHandler 的類)只是個打嘴炮的人。
回到正題,在真正做解析工作的時候,會首先創建一個委託類BeanDefinitionParserDelegate ,那麼先來認識一下這個類。
// 用於解析XML bean定義的有狀態委託類。 旨在供主解析器和任何擴展使用 public class BeanDefinitionParserDelegate { // 此方法也就是委託給 BeanDefinitionParserDelegate 去做的事情 protected BeanDefinitionParserDelegate createDelegate( XmlReaderContext readerContext, Element root, @Nullable BeanDefinitionParserDelegate parentDelegate) { BeanDefinitionParserDelegate delegate = new BeanDefinitionParserDelegate(readerContext); delegate.initDefaults(root, parentDelegate); return delegate; } } 它主要完成的事情在initDefaults 方法中,一起看下這個方法
/* * 初始化默認的 懶載入,自動注入,依賴檢查設置, 初始化方法,銷毀方法和合併設置。 * 如果未在本地顯式設置默認值,則通過回退到給定父級來支援嵌套的「beans」元素用例。 */ public void initDefaults(Element root, @Nullable BeanDefinitionParserDelegate parent) { populateDefaults(this.defaults, (parent != null ? parent.defaults : null), root); this.readerContext.fireDefaultsRegistered(this.defaults); } /** * in case the defaults are not explicitly set locally. * 使用默認的 lazy-init,autowire,依賴檢查設置,init-method,destroy-method 和 合併設置 屬性 填充給定的 * DocumentDefaultsDefinition,如果未在本地顯式設置默認值,則支援嵌套的'beans'元素用例,返回parentDefaults */ protected void populateDefaults(DocumentDefaultsDefinition defaults, @Nullable DocumentDefaultsDefinition parentDefaults, Element root) { // default-lazy-init String lazyInit = root.getAttribute(DEFAULT_LAZY_INIT_ATTRIBUTE); if (DEFAULT_VALUE.equals(lazyInit)) { // Potentially inherited from outer <beans> sections, otherwise falling back to false. lazyInit = (parentDefaults != null ? parentDefaults.getLazyInit() : FALSE_VALUE); } defaults.setLazyInit(lazyInit); // 餘下的程式碼和lazyinit 的邏輯相同,也是解析方法注釋中的各種屬性。 這裡礙於篇幅原因暫不貼出了 ... // defaults.setSource(this.readerContext.extractSource(root)); } 然後方法一直返回到創建委託的入口也就是 BeanDefinitionParserDelegate parent = this.delegate;,然後是 Spring 留給開發人員的兩個介面,用於開發人員自己實現
... // 在解析Bean定義之前,進行自定義的解析,增強解析過程的可擴展性 preProcessXml(root); // 委託給 BeanDefinitionParserDelegate,從Document的根元素開始進行BeanDefinition的解析 parseBeanDefinitions(root, this.delegate); // 在解析Bean定義之後,進行自定義的解析,增加解析過程的可擴展性 postProcessXml(root); 我們再來看一下 parseBeanDefinitions(root, this.delegate); 這個方法是交給剛剛創建的 BeanDefinitionParserDelegate 委託用來解析具體的 lazy-init 等屬性的標籤的
/** * 解析文檔對象的根目錄節點: import, alias, bean */ protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { // 載入的Document對象是否使用了Spring默認的XML命名空間(beans命名空間) if (delegate.isDefaultNamespace(root)) { // 獲取Document對象根元素的所有子節點(bean標籤、import標籤、alias標籤和其他自定義標籤context、aop等) 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; // bean標籤、import標籤、alias標籤,則使用默認解析規則 if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else {//像context標籤、aop標籤、tx標籤,則使用用戶自定義的解析規則解析元素節點 delegate.parseCustomElement(ele); } } } } else { // 如果不是默認的命名空間,則使用用戶自定義的解析規則解析元素節點 delegate.parseCustomElement(root); } } 上述步驟主要分為三步:
- 先判斷是否委託類使用Spring默認的XML命名空間(beans命名空間),在判斷如果取出來的節點是bean、import、alias的話就使用默認的解析規則
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>標籤 else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } // 解析內置<bean>標籤 else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse // 遞歸調用 doRegisterBeanDefinitions(ele); } } - 如果判斷取出來的不是默認節點,則會使用自定義的默認解析規則,context標籤,aop標籤, tx標籤則會默認使用此解析方法
@Nullable public BeanDefinition parseCustomElement(Element ele) { // 解析自定義標籤 return parseCustomElement(ele, null); } @Nullable public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { // 獲取命名空間URI(就是獲取beans標籤的xmlns:aop或者xmlns:context屬性的值) String namespaceUri = getNamespaceURI(ele); if (namespaceUri == null) { return null; } // 根據不同的命名空間URI,去匹配不同的NamespaceHandler(一個命名空間對應一個NamespaceHandler) // 此處會調用DefaultNamespaceHandlerResolver類的resolve方法 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } // 調用匹配到的NamespaceHandler的解析方法 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); } - 如果不是使用 beans 的命名空間,也會使用自定義的解析方法進行解析
然後方法解析完成,一直返回到 AbstractApplicationContext 中的 刷新 bean Factory 的方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { // 主要是通過該方法完成IoC容器的刷新 // ClassPathXmlApplicationContext.refreshBeanFactory 調用的是 AbstractRefreshApplicationContext // AnnotationConfigApplicationContext.refreshBeanFactory調用的是GenericApplicationContext refreshBeanFactory(); ConfigurableListableBeanFactory beanFactory = getBeanFactory(); if (logger.isDebugEnabled()) { logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory); } return beanFactory; } 這樣取出的 Bean 對象就可以進行使用了。
下一篇文章來 解析一下第三個步驟: 對IoC容器進行一些預處理