Spring IoC 容器初始化
概述
上篇文章「Spring 中的 IoC 容器」從整體介紹了 Spring IoC 容器的相關(guān)概念和大致實現(xiàn)流程,本文要進(jìn)入源碼來一探究竟了。
這里仍以前文的代碼為例進(jìn)行分析,測試代碼如下:
public?class?IocTests?{
????@Test
????public?void?test01()?{
????????ApplicationContext?context?=?new?ClassPathXmlApplicationContext("application-ioc.xml");
????????System.out.println(context.getBean("person"));
????????System.out.println(context.getBean("dog"));
????}
}
/*
?*?輸出結(jié)果:
?*??Person{id=12,?name='Jack-12'}
?*??Dog{age=1}
?*/
PS: 此處 Spring Framework 版本為 5.2.12.RELEASE,其他版本可能略有不同。
代碼分析
從 ClassPathXmlApplicationContext 的構(gòu)造器進(jìn)入它的代碼。
ClassPathXmlApplicationContext 有很多重載的構(gòu)造器,不過多數(shù)都會調(diào)用到下面這個:
public?ClassPathXmlApplicationContext(
??String[]?configLocations,?boolean?refresh,?@Nullable?ApplicationContext?parent)
??throws?BeansException?{
?//?調(diào)用父類構(gòu)造器,保存?zhèn)魅氲母溉萜?/span>
?super(parent);
????
?//?保存配置文件信息
?setConfigLocations(configLocations);
????
?//?刷新?IoC?容器
?if?(refresh)?{
??refresh();
?}
}
該構(gòu)造器主要做了三件事:
調(diào)用父類的構(gòu)造器,保存?zhèn)魅氲母溉萜?/section> 保存配置信息,在本例中就是 application-ioc.xml 刷新 IoC 容器
其中最核心的就是第三步,也是最復(fù)雜的。
由于 ClassPathXmlApplicationContext 的整體繼承結(jié)構(gòu)比較復(fù)雜,為了便于分析其核心實現(xiàn),這里先暫時忽略它實現(xiàn)的接口,只看它的類繼承結(jié)構(gòu):

前面兩個步驟的代碼不再深入分析,這里直接進(jìn)入第三步,也就是 refresh 方法(該方法是在 AbstractApplicationContext 類中實現(xiàn)的):
public?abstract?class?AbstractApplicationContext?extends?DefaultResourceLoader
??implements?ConfigurableApplicationContext?{
?//?...
?@Override
?public?void?refresh()?throws?BeansException,?IllegalStateException?{
??synchronized?(this.startupShutdownMonitor)?{
???//?Prepare?this?context?for?refreshing.
???prepareRefresh();
???//?獲取?BeanFactory
???//?Tell?the?subclass?to?refresh?the?internal?bean?factory.
???ConfigurableListableBeanFactory?beanFactory?=?obtainFreshBeanFactory();
???//?Prepare?the?bean?factory?for?use?in?this?context.
???prepareBeanFactory(beanFactory);
???try?{
????//?Allows?post-processing?of?the?bean?factory?in?context?subclasses.
????postProcessBeanFactory(beanFactory);
????//?Invoke?factory?processors?registered?as?beans?in?the?context.
????invokeBeanFactoryPostProcessors(beanFactory);
????//?Register?bean?processors?that?intercept?bean?creation.
????registerBeanPostProcessors(beanFactory);
????//?Initialize?message?source?for?this?context.
????initMessageSource();
????//?Initialize?event?multicaster?for?this?context.
????initApplicationEventMulticaster();
????//?Initialize?other?special?beans?in?specific?context?subclasses.
????onRefresh();
????//?Check?for?listener?beans?and?register?them.
????registerListeners();
????//?Instantiate?all?remaining?(non-lazy-init)?singletons.
????finishBeanFactoryInitialization(beanFactory);
????//?Last?step:?publish?corresponding?event.
????finishRefresh();
???}
???//?catch?...
???//?finally?...
??}
?}
?//?...
?protected?ConfigurableListableBeanFactory?obtainFreshBeanFactory()?{
??refreshBeanFactory();
??return?getBeanFactory();
?}????
}
refresh 方法里面封裝了很多方法,每個方法里面又是一大堆代碼……
剛開始看到這里可能會被嚇到(我當(dāng)初就是這樣被勸退的)。
其實不必,代碼雖然很多,但讀起來還是有跡可循的:就是要先找到一條主線。就像一棵樹,先找到樹干,其它的都是細(xì)枝末節(jié)。先主后次,要不很容易陷進(jìn)去、讀起來一團亂麻。
PS: 之前寫過一篇如何閱讀 JDK 源碼的文章「我是如何閱讀JDK源碼的?」,有興趣可以參考一下。
但二者又有些不同:JDK 源碼相對獨立,一般關(guān)聯(lián)性不大,而 Spring 的代碼前后關(guān)聯(lián)太多。
這里的主線是什么呢?
就是 obtainFreshBeanFactory 方法(它的實現(xiàn)在 AbstractRefreshableApplicationContext 類中),如下:
public?abstract?class?AbstractRefreshableApplicationContext?extends?AbstractApplicationContext?{
?//?...
?@Override
?protected?final?void?refreshBeanFactory()?throws?BeansException?{
??//?如果容器已經(jīng)存在,則銷毀容器中的對象、關(guān)閉容器
??if?(hasBeanFactory())?{
???destroyBeans();
???closeBeanFactory();
??}
??try?{
???//?創(chuàng)建?BeanFactory
???DefaultListableBeanFactory?beanFactory?=?createBeanFactory();
???beanFactory.setSerializationId(getId());
???customizeBeanFactory(beanFactory);
???//?加載?BeanDefinition
???loadBeanDefinitions(beanFactory);
???this.beanFactory?=?beanFactory;
??}
??catch?(IOException?ex)?{
???throw?new?ApplicationContextException("I/O?error?parsing?bean?definition?source?for?"?+?getDisplayName(),?ex);
??}
?}
?//?...
????
?protected?abstract?void?loadBeanDefinitions(DefaultListableBeanFactory?beanFactory)
???throws?BeansException,?IOException;????
}
refreshBeanFactory 看名字可以推測是刷新 IoC 容器,它主要做了三件事:
如果 IoC 容器已經(jīng)存在,則銷毀容器中的對象、關(guān)閉容器(正如其名,refresh,是一個全新的,就要先把舊的干掉)。 創(chuàng)建 BeanFactory,即 DefaultListableBeanFactory,它就是 Spring IoC 容器的默認(rèn)實現(xiàn)。 加載 BeanDefinition,也就是從配置文件(application-ioc.xml)中加載我們定義的 bean 信息,這一步也是最復(fù)雜的。
這里的 loadBeanDefinitions 是一個抽象方法?!
是的。這就是設(shè)計模式中的「模板模式(Template Pattern)」,這個模式不難理解,不了解也不影響,有空可以再看。
JDK 中的 AbstractQueuedSynchronizer(AQS) 也使用了模板模式,前文「JDK源碼分析-AbstractQueuedSynchronizer(2)」可以參考。
loadBeanDefinitions 方法其實就是把實現(xiàn)代碼放在子類中了。
What?它的子類有那么多,哪個才是真·兒子呢?
還記得上面那個類的繼承結(jié)構(gòu)圖嗎?
所以,這里它的子類實現(xiàn)指的就是 AbstractXmlApplicationContext#loadBeanDefinitions 方法:
public?abstract?class?AbstractXmlApplicationContext?extends?AbstractRefreshableConfigApplicationContext?{
?//?...
?@Override
?protected?void?loadBeanDefinitions(DefaultListableBeanFactory?beanFactory)?throws?BeansException,?IOException?{
??//?創(chuàng)建?BeanDefinitionReader,用于讀取?BeanDefinition
??//?Create?a?new?XmlBeanDefinitionReader?for?the?given?BeanFactory.
??XmlBeanDefinitionReader?beanDefinitionReader?=?new?XmlBeanDefinitionReader(beanFactory);
??//?Configure?the?bean?definition?reader?with?this?context's
??//?resource?loading?environment.
??beanDefinitionReader.setEnvironment(this.getEnvironment());
??beanDefinitionReader.setResourceLoader(this);
??beanDefinitionReader.setEntityResolver(new?ResourceEntityResolver(this));
??//?Allow?a?subclass?to?provide?custom?initialization?of?the?reader,
??//?then?proceed?with?actually?loading?the?bean?definitions.
??initBeanDefinitionReader(beanDefinitionReader);
??loadBeanDefinitions(beanDefinitionReader);
?}
?//?...
????
?protected?void?loadBeanDefinitions(XmlBeanDefinitionReader?reader)?throws?BeansException,?IOException?{
??//?讀取?Resource?類型的配置
??Resource[]?configResources?=?getConfigResources();
??if?(configResources?!=?null)?{
???reader.loadBeanDefinitions(configResources);
??}
??//?讀取?String?類型的配置
??String[]?configLocations?=?getConfigLocations();
??if?(configLocations?!=?null)?{
???reader.loadBeanDefinitions(configLocations);
??}
?}????
}
它來了,XmlBeanDefinitionReader,是用來讀取 BeanDefinition 的。
這里 BeanDefinition 的類型有兩種:Resource 和 String。其實本質(zhì)上還是一種,即 Resource,String 最終還是要轉(zhuǎn)為 Resource。
這兩個 loadBeanDefinitions 最終都會調(diào)用 XmlBeanDefinitionReader#loadBeanDefinitions 方法:
public?class?XmlBeanDefinitionReader?extends?AbstractBeanDefinitionReader?{
?//?...
?@Override
?public?int?loadBeanDefinitions(Resource?resource)?throws?BeanDefinitionStoreException?{
??return?loadBeanDefinitions(new?EncodedResource(resource));
?}
?public?int?loadBeanDefinitions(EncodedResource?encodedResource)?throws?BeanDefinitionStoreException?{
??Assert.notNull(encodedResource,?"EncodedResource?must?not?be?null");
??if?(logger.isTraceEnabled())?{
???logger.trace("Loading?XML?bean?definitions?from?"?+?encodedResource);
??}
??Set?currentResources?=?this.resourcesCurrentlyBeingLoaded.get();
??if?(!currentResources.add(encodedResource))?{
???throw?new?BeanDefinitionStoreException(
?????"Detected?cyclic?loading?of?"?+?encodedResource?+?"?-?check?your?import?definitions!");
??}
??//?讀取配置文件
??try?(InputStream?inputStream?=?encodedResource.getResource().getInputStream())?{
???InputSource?inputSource?=?new?InputSource(inputStream);
???if?(encodedResource.getEncoding()?!=?null)?{
????inputSource.setEncoding(encodedResource.getEncoding());
???}
???//?實際加載?BeanDefinition
???return?doLoadBeanDefinitions(inputSource,?encodedResource.getResource());
??}
??//?catch?...
?}
?protected?int?doLoadBeanDefinitions(InputSource?inputSource,?Resource?resource)
???throws?BeanDefinitionStoreException?{
??try?{
???//?將?Resource?解析為?Document?對象
???Document?doc?=?doLoadDocument(inputSource,?resource);
???//?從?Document?解析和注冊?BeanDefinition
???int?count?=?registerBeanDefinitions(doc,?resource);
???if?(logger.isDebugEnabled())?{
????logger.debug("Loaded?"?+?count?+?"?bean?definitions?from?"?+?resource);
???}
???return?count;
??}
??//?catch?...
?}????
?//?...
}
loadBeanDefinitions 做了層封裝,主要工作是在 doLoadBeanDefinitions 實現(xiàn)的。doLoadBeanDefinitions 方法主要做了兩件事:
將 Resource 解析為 Document 對象。 從 Document 解析和注冊 BeanDefinition。
第一步只是工具人,不必深究。
關(guān)鍵在于第二步,也比較復(fù)雜,后文再分析吧,畢竟源碼讀起來還是有點累……本文就先到這了。
Spring 源碼中的關(guān)鍵部分通常是有點小規(guī)律的,比如:
以 try...catch 包圍 以 do 開頭的方法才是實際做事的,前面都是 caller,比如 doLoadBeanDefinitions、doGetBean 等 其他規(guī)律小伙伴們可以繼續(xù)補充。
小結(jié)
本文開始進(jìn)入 Spring IoC 容器的源碼了,主要找到了一條主線,為便于對整體有個了解,這里簡單總結(jié)了一個思維導(dǎo)圖(還有后面的預(yù)告):

小伙伴們讀的時候也可以用思維導(dǎo)圖工具畫一下主要流程,起到提綱挈領(lǐng)的作用,也便于以后回顧。
源碼讀起來還是需要一點耐心的,畢竟它就是這么樸實無華……且枯燥??

