IOC容器的初始化是又refresh()方法啓動的,這個方法標誌着IOC容器的正式啓動。具體來說,這個啓動包括bean的resource定位,載入和註冊三個過程。今天我們主要了解資源定位的過程。
定位
以編程的方式使用DefaultListableBeanFactory時,首先定義一個Resource來定位容器使用的BeanDefinition。這時使用的是ClassPathResource,這意味着Spring會在類路徑中去尋找以文件形式存在的BeanDefinition信息。
ClassPathResource res=new ClassPathResource("beans.xml");
這裏定義的Resource並不能由DefaultListableBeanFactory直接使用,Spring通過BeanDefinitionReader來對這些信息進行處理。我們也可以看到使用ApplicationContext 相對於直接使用DefaultListableBeanFactory的好處。因爲在ApplicationContext中,spring已經爲我們提供了一系列加載不同Resource的讀取器的實現,而DefaultListableBeanFactory只是一個純粹的IoC容器,需要爲它配置特定的讀取器才能完成這些功能。
當然, 有利就有弊, 使用DefaultListableBeanFactory這種更底層的容器,能提高定製IoC容器的靈活性。
FilesystemXmlApplicationContext可以從文件系統載入Resource,ClassPathXmlApplicationContext可以從ClassPath載入Resource,,XmlWebApplicationContext可以在Web容器中載入Resource。
以FileSystemXmlApplicationContext爲例, 通過分析這個ApplicationContext的實現來看看它是怎樣完成這個Resource定位過程的。下面是這個ApplicationContext的繼承體系。
圖1
FilesystemXmlApplicationContext的繼承體系
從源代碼角度看:
圖2 代碼角度看FilesystemXmlApplicationContext的繼承體系
FilesystemXmlApplicationContext.class
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
-
public FileSystemXmlApplicationContext() {
}
-
public FileSystemXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
/**
* Create a new FileSystemXmlApplicationContext, loading the definitions
* from the given XML file and automatically refreshing the context.
* @param configLocation file path
* @throws BeansException if context creation failed
*/
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
/**
* Create a new FileSystemXmlApplicationContext, loading the definitions
* from the given XML files and automatically refreshing the context.
* @param configLocations array of file paths
* @throws BeansException if context creation failed
*/
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
/**
* Create a new FileSystemXmlApplicationContext with the given parent,
* loading the definitions from the given XML files and automatically
* refreshing the context.
* @param configLocations array of file paths
* @param parent the parent context
* @throws BeansException if context creation failed
*/
public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
this(configLocations, true, parent);
}
/**
* Create a new FileSystemXmlApplicationContext, loading the definitions
* from the given XML files.
* @param configLocations array of file paths
* @param refresh whether to automatically refresh the context,
* loading all bean definitions and creating all singletons.
* Alternatively, call refresh manually after further configuring the context.
* @throws BeansException if context creation failed
* @see #refresh()
*/
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, null);
}
/**
* Create a new FileSystemXmlApplicationContext with the given parent,
* loading the definitions from the given XML files.
* @param configLocations array of file paths
* @param refresh whether to automatically refresh the context,
* loading all bean definitions and creating all singletons.
* Alternatively, call refresh manually after further configuring the context.
* @param parent the parent context
* @throws BeansException if context creation failed
* @see #refresh()
*/
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
/**
* Resolve resource paths as file system paths.
* <p>Note: Even if a given path starts with a slash, it will get
* interpreted as relative to the current VM working directory.
* This is consistent with the semantics in a Servlet container.
* @param path path to the resource
* @return Resource handle
* @see org.springframework.web.context.support.XmlWebApplicationContext#getResourceByPath
*/
@Override
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
}
我們可以看到在構造函數中,實現了對configuration進行處理的功能。讓所有配置在文件系統中的,以.XML 文件方式存在的BeanDefnition都能夠得到有效的處理。實現了 getResourceByPath方法,這個方法是個模板方法,是爲讀取Resource服務的。對於IoC容器功能的實現,這裏沒有涉及,因爲它繼承了AbstractXmlApplicationContext
。關於IoC容器功能相關的實現,都是在FileSysternXmlApplicationContext中完成的,但是在構造函數中通過refresh來啓動IoC容器的初始化,這個refresh方法非常重要,也是我們以後分析容器初始化過程實現的一個重要入口。
關於讀入器的配置,可以到它的基類AbstractXmlApplicationContext中查看。
圖3 getResourceByPath的調用關係
圖4 getResourceByPath的調用過程
我們重點AbstractRefreshableApplicationContext的refreshBeanFactory方法的實現。
AbstractRefreshableApplicationContext.class
protected final void refreshBeanFactory() throws BeansException {
//這裏判斷,如果已經建立了BeanFactory,則銷燬並關閉該工廠。
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
//創建並設置持有的DefaultListableBeanFactory的地方同時調用loadBeanDefinitions載入beandefinition信息
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
//在上下文中創建DefaultListableBeanFactory的地方載入bean定義,因爲允許有多種載入方式,雖然用得最多的是XML定義的形式,
//這裏通過一個抽象函數把具體的實現委託給子類來完成
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException;
AbstractBeanDefinitionReader.class
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//這裏取得ResourceLoader,使用的是DefaultResourceLoader
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//對Resource的路徑模式進行解析,得到需要的Resource集合
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
// Can only load single resources by absolute URL.
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
DefaultResourceLoader.class
//對於取得Resource的具體過程,看DefaultResourceLoader是怎樣完成的。
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
//這裏處理帶有classpath標識的Resource
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
//這裏處理帶有URL標識的Resource
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
//兩者都不是,用此方法,默認得到一個ClassPathContextResource,這個方法常用子類來實現
//前面看到子類FilesystemXmlApplicationContext實現此方法,返回一個Filesystemresource。
return getResourceByPath(location);
}
}
}
其他ApplicationContext會對應生成其他種類的Resource。下圖中我們可以看到Resource類的繼承關係。
圖5 Resource的定義和繼承關係
我們通過對FilesystemXmlApplicationContext的實現原理爲例,瞭解了Resource定位問題的解決方案。定位完成,接下來就是對返回的Resource對象進行載入了。接下來我們會介紹BeanDdfinition的載入和解析過程。