Spring原始碼分析之-載入IOC容器
本文接上一篇文章 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容器進行一些預處