在Spring IoC
容器的設計中,有兩個主要的容器系列。一個是實現了BeanFactory
接口的簡單容器系列,這系列容器只實現了容器基本的功能;另外一個是ApplicationContext
應用上下文,它在簡單容器的基礎上增長了許多面向框架的特性,同時對應用環境作了許多適配。java
Spring IoC
容器的初始化過程分爲三個階段:Resource
定位、BeanDefinition
的載入和向IoC
容器註冊BeanDefinition
。Spring
把這三個階段分離,並使用不一樣的模塊來完成,這樣可讓用戶更加靈活的對這三個階段進行擴展。數據結構
Resource
定位指的是BeanDefinition
的資源定位,它由ResourceLoader
經過統一的Resource
接口來完成,Resource
對各類形式的BeanDefinition
的使用都提供了統一的接口。BeanDefinition
的載入是把用戶定義好的Bean
表示成IoC
容器內部的數據結構,而這個容器內部的數據結構就是BeanDefinition
,BeanDefinition
實際上就是POJO
對象在IoC
容器中的抽象。經過BeanDefinition
,IoC
容器能夠方便的對POJO
對象進行管理。IoC
容器註冊BeanDefinition
是經過調用BeanDefinitionRegistry
接口的實現來完成的,這個註冊過程是把載入的BeanDefinition
向IoC
容器進行註冊。實際上,在IoC
容器內部維護着一個HashMap
,而這個註冊過程其實就將BeanDefinition
添加至這個HashMap
。咱們能夠本身定義Resource
、BeanFactory
和BeanDefinitionReader
來初始化一個容器。以下代碼片斷使用了DefaultListableBeanFactory
做爲實際使用的IoC
容器。同時,建立IoC
配置文件(dispatcher-servlet.xml
)的抽象資源,這個抽象資源包含了BeanDefinition
的定義信息。最後,還須要建立一個載入BeanDefinition
的讀取器,此處使用XmlBeanDefinitionReader
,經過一個回調配置給BeanFactory
。框架
ClassPathResource res = new ClassPathResource("dispatcher-servlet.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(res);複製代碼
咱們也能夠經過ApplicationContext
建立一個IoC
容器。在Spring
中,系統已經提供許多定義好的容器實現,而不須要本身組裝。以下代碼片斷以FileSystemXmlApplicationContext
爲例建立了一個IoC
容器。post
FileSystemXmlApplicationContext context =
new FileSystemXmlApplicationContext("classpath:dispatcher-servlet.xml");複製代碼
不管使用哪一種方式初始化IoC
容器,都會經歷上述三個階段。本篇文章將結合Spring 4.0.2
源碼,並以FileSystemXmlApplicationContext
爲例對IoC
容器初始化的第一階段,也就是Resource
定位階段進行分析。this
下圖展現了FileSystemXmlApplicationContext
的繼承體系,FileSystemXmlApplicationContext
繼承自AbstractApplicationContext
,而AbstractApplicationContext
又繼承自DefaultResourceLoader
,DefaultResourceLoader
實現了ResourceLoader
接口。所以FileSystemXmlApplicationContext
具有讀取定義了BeanDefinition
的Resource
的能力。spa
咱們的分析入口是new FileSystemXmlApplicationContext("classpath:dispatcher-servlet.xml");
,這句代碼調用了FileSystemXmlApplicationContext
的構造方法。FileSystemXmlApplicationContext
的構造方法源碼以下(只提取與本次分析關聯的代碼)。debug
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[]{configLocation}, true, (ApplicationContext)null);
}
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if(refresh) {
this.refresh();
}
}複製代碼
在建立FileSystemXmlApplicationContext
時,咱們僅傳入了包含BeanDefinition
的配置文件路徑(classpath:dispatcher-servlet.xml
),由此調用FileSystemXmlApplicationContext(String configLocation)
構造方法。接着,FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
構造方法被間接調用,在該構造方法內部,refresh
方法完成了整個IoC
容器的初始化。所以,refresh
方法是咱們分析的下一個入口。設計
refresh
方法的具體實現定義在FileSystemXmlApplicationContext
的父類AbstractApplicationContext
中,對應的源碼以下。code
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var5) {
this.destroyBeans();
this.cancelRefresh(var5);
throw var5;
}
}
}複製代碼
在refresh
方法中,經過obtainFreshBeanFactory
方法,ConfigurableListableBeanFactory
類型的BeanFactory
被建立。咱們接着進入obtainFreshBeanFactory
方法,obtainFreshBeanFactory
方法也定義在AbstractApplicationContext
中。orm
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
this.refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
if(this.logger.isDebugEnabled()) {
this.logger.debug("Bean factory for " + this.getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;複製代碼
咱們重點關注refreshBeanFactory
方法的實現。在AbstractApplicationContext
中,refreshBeanFactory
方法僅僅是個聲明,具體的實現委託給了子類完成。此處,refreshBeanFactory
方法的具體實現定義在了AbstractRefreshableApplicationContext
,AbstractRefreshableApplicationContext
正是繼承自AbstractApplicationContext
,這點咱們能夠從上文的繼承體系圖能夠得知。refreshBeanFactory
方法在AbstractRefreshableApplicationContext
中的定義以下。
protected final void refreshBeanFactory() throws BeansException {
if(this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}
try {
DefaultListableBeanFactory ex = this.createBeanFactory();
ex.setSerializationId(this.getId());
this.customizeBeanFactory(ex);
this.loadBeanDefinitions(ex);
Object var2 = this.beanFactoryMonitor;
synchronized(this.beanFactoryMonitor) {
this.beanFactory = ex;
}
} catch (IOException var5) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
}
}
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory var1) throws BeansException, IOException;複製代碼
refreshBeanFactory
方法首先會判斷是否已經創建的BeanFactory
,若是已經創建,那麼須要銷燬並關閉該BeanFactory
。接着,refreshBeanFactory
方法經過createBeanFactory
方法建立了一個IoC
容器供ApplicationContext
使用,且這個IoC
容器的實際類型爲DefaultListableBeanFactory
。同時,refreshBeanFactory
方法將這個IoC
容器做爲參數,調用loadBeanDefinitions
載入了BeanDefinition
(本文暫不分析載入過程的具體操做)。
loadBeanDefinitions
方法也僅僅在AbstractRefreshableApplicationContext
中聲明,具體的實現定義在AbstractXmlApplicationContext
中,從繼承體系圖咱們能夠得知AbstractXmlApplicationContext
正是AbstractRefreshableApplicationContext
的子類。loadBeanDefinitions
方法對應的源碼以下。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
this.initBeanDefinitionReader(beanDefinitionReader);
this.loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = this.getConfigResources();
if(configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = this.getConfigLocations();
if(configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}複製代碼
在loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
中,定義了BeanDefinition
的讀入器beanDefinitionReader
。Spring
把定位、讀入和註冊的過程解耦,這正是體現之處之一。接着beanDefinitionReader
做爲參數,調用loadBeanDefinitions(XmlBeanDefinitionReader reader)
方法,若是configResources
爲空,那麼reader
就會根據configLocations
調用reader
的loadBeanDefinitions
去加載相應的Resource
。在AbstractBeanDefinitionReader
和XmlBeanDefinitionReader
中個自定義了不一樣的loadBeanDefinitions
方法,與咱們本次分析相關的代碼定義在AbstractBeanDefinitionReader
中,以下所示。
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
String[] var3 = locations;
int var4 = locations.length;
for(int var5 = 0; var5 < var4; ++var5) {
String location = var3[var5];
counter += this.loadBeanDefinitions(location);
}
return counter;
}
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(location, (Set)null);
}
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = this.getResourceLoader();
if(resourceLoader == null) {
throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
} else {
int loadCount;
if(!(resourceLoader instanceof ResourcePatternResolver)) {
Resource var11 = resourceLoader.getResource(location);
loadCount = this.loadBeanDefinitions((Resource)var11);
if(actualResources != null) {
actualResources.add(var11);
}
if(this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
} else {
try {
Resource[] resource =
((ResourcePatternResolver)resourceLoader).getResources(location);
loadCount = this.loadBeanDefinitions(resource);
if(actualResources != null) {
Resource[] var6 = resource;
int var7 = resource.length;
for(int var8 = 0; var8 < var7; ++var8) {
Resource resource1 = var6[var8];
actualResources.add(resource1);
}
}
if(this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
} catch (IOException var10) {
throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var10);
}
}
}
}複製代碼
在loadBeanDefinitions(String location, Set<Resource> actualResources)
方法中,咱們能夠看到,Resource
的定位工做交給了ResourceLoader
來完成。對於取得Resource
的具體過程,咱們能夠看看DefaultResourceLoader
是怎樣完成的,對應源碼以下。
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if(location.startsWith("classpath:")) {
return new ClassPathResource(location.substring("classpath:".length()),
this.getClassLoader());
} else {
try {
URL ex = new URL(location);
return new UrlResource(ex);
} catch (MalformedURLException var3) {
return this.getResourceByPath(location);
}
}
}複製代碼
因爲咱們傳入的location
爲classpath:dispatcher-servlet.xml
,所以getResource
方法會生成一個ClassPathResource
並返回,若是咱們傳入的是一個文件路徑,那麼會調用getResourceByPath
方法,getResourceByPath
方法定義在FileSystemXmlApplicationContext
中,對應的源碼以下。
protected Resource getResourceByPath(String path) {
if(path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}複製代碼
到此,咱們完成了IoC
容器在初始化過程當中的Resource
定位過程的流程分析,這爲接下來進行BeanDefinition
數據的載入和解析創造了條件。
後續我會對BeanDefinition
的載入和解析過程結合源碼進行分析,歡迎關注。若本文存在分析不妥之處,建議發送郵件至tinylcy (at) gmail.com
交流。