在Spring IoC
容器的設計中,有兩個主要的容器系列。一個是實現了BeanFactory
介面的簡單容器系列,這系列容器只實現了容器基本的功能;另一個是ApplicationContext
應用上下文,它在簡單容器的基礎上增加了許多面向框架的特性,同時對應用環境做了許多適配。
IoC容器的初始化過程
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
容器。
FileSystemXmlApplicationContext context =
new FileSystemXmlApplicationContext("classpath:dispatcher-servlet.xml");複製程式碼
無論使用哪種方式初始化IoC
容器,都會經歷上述三個階段。本篇文章將結合Spring 4.0.2
原始碼,並以FileSystemXmlApplicationContext
為例對IoC
容器初始化的第一階段,也就是Resource
定位階段進行分析。
BeanDefinition的Resource定位
下圖展示了FileSystemXmlApplicationContext
的繼承體系,FileSystemXmlApplicationContext
繼承自AbstractApplicationContext
,而AbstractApplicationContext
又繼承自DefaultResourceLoader
,DefaultResourceLoader
實現了ResourceLoader
介面。因此FileSystemXmlApplicationContext
具備讀取定義了BeanDefinition
的Resource
的能力。
我們的分析入口是new FileSystemXmlApplicationContext("classpath:dispatcher-servlet.xml");
,這句程式碼呼叫了FileSystemXmlApplicationContext
的構造方法。FileSystemXmlApplicationContext
的構造方法原始碼如下(只提取與本次分析關聯的程式碼)。
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
中,對應的原始碼如下。
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
中。
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
交流。