我們知道Spring的IoC起到了一個容器的作用,其中裝得都是各種各樣的Bean。同時在我們剛剛開始學習Spring的時候都是通過xml文件來定義Bean,Spring會某種方式加載這些xml文件,然後根據這些信息綁定整個系統的對象,最終組裝成一個可用的基於輕量級容器的應用系統。
Spring IoC容器整體可以劃分為兩個階段,容器啟動階段,Bean實例化階段。其中容器啟動階段主要包括加載配置信息、解析配置信息,裝備到BeanDefinition中以及其他後置處理,而Bean實例化階段主要包括實例化對象,裝配依賴,生命周期管理已經注冊回調。下面LZ先介紹容器的啟動階段的第一步,即定位配置文件。
我們使用編程方式使用DefaultListableBeanFactory時,首先是需要定義一個Resource來定位容器使用的BeanDefinition。
ClassPathResource resource = new ClassPathResource("bean.xml");
通過這個代碼,就意味著Spring會在類路徑中去尋找bean.xml並解析為BeanDefinition信息。當然這個Resource並不能直接被使用,他需要被BeanDefinitionReader進行解析處理(這是後面的內容了)。
對於各種applicationContext,如FileSystemXmlApplicationContext、ClassPathXmlApplicationContext等等,我們從這些類名就可以看到他們提供了那些Resource的讀入功能。下面我們以FileSystemXmlApplicationContext為例來闡述Spring IoC容器的Resource定位。
先看FileSystemXmlApplicationContext繼承體系結構:
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
/**
* 默認構造函數
*/
public FileSystemXmlApplicationContext() {
}
public FileSystemXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, null);
}
public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
this(configLocations, true, parent);
}
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh) throws BeansException {
this(configLocations, refresh, null);
}
//核心構造器
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
//通過構造一個FileSystemResource對象來得到一個在文件系統中定位的BeanDefinition
//采用模板方法設計模式,具體的實現用子類來完成
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
}
AbstractApplicationContext中的refresh()方法是IoC容器初始化的入口,也就是說IoC容器的初始化是通過refresh()方法來完成整個調用過程的。在核心構造器中就對refresh進行調用,通過它來啟動IoC容器的初始化工作。getResourceByPath為一個模板方法,通過構造一個FileSystemResource對象來得到一個在文件系統中定位的BeanDEfinition。getResourceByPath的調用關系如下(部分):
protected final void refreshBeanFactory() throws BeansException {
//判斷是否已經創建了BeanFactory,如果創建了則銷毀關閉該BeanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//創建DefaultListableBeanFactory實例對象
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//加載BeanDefinition信息
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作為BeanFactory默認實現類,其重要性不言而喻,而createBeanFactory()則返回該實例對象。
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
loadBeanDefinition方法加載BeanDefinition信息,BeanDefinition就是在這裡定義的。AbstractRefreshableApplicationContext對loadBeanDefinitions僅僅只是定義了一個抽象的方法,真正的實現類為其子類AbstractXmlApplicationContext來實現:
AbstractRefreshableApplicationContext:
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws BeansException, IOException;
AbstractXmlApplicationContext:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//創建bean的讀取器(Reader),即XmlBeanDefinitionReader,並通過回調設置到容器中
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
//
beanDefinitionReader.setEnvironment(getEnvironment());
//為Bean讀取器設置Spring資源加載器
beanDefinitionReader.setResourceLoader(this);
//為Bean讀取器設置SAX xml解析器
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//
initBeanDefinitionReader(beanDefinitionReader);
//Bean讀取器真正實現的地方
loadBeanDefinitions(beanDefinitionReader);
}
程序首先首先創建一個Reader,在前面就提到過,每一類資源都對應著一個BeanDefinitionReader,BeanDefinitionReader提供統一的轉換規則;然後設置Reader,最後調用loadBeanDefinition,該loadBeanDefinition才是讀取器真正實現的地方:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//獲取Bean定義資源的定位
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//獲取Bean定義資源的路徑。在FileSystemXMLApplicationContext中通過setConfigLocations可以配置Bean資源定位的路徑
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
首先通過getConfigResources()獲取Bean定義的資源定位,如果不為null則調用loadBeanDefinitions方法來讀取Bean定義資源的定位。
loadBeanDefinitions是中的方法:
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
for (String location : locations) {
counter += loadBeanDefinitions(location);
}
return counter;
}
繼續:
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
再繼續:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//獲取ResourceLoader資源加載器
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//
if (resourceLoader instanceof ResourcePatternResolver) {
try {
//調用DefaultResourceLoader的getResourceByPath完成具體的Resource定位
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 {
//調用DefaultResourceLoader的getResourceByPath完成具體的Resource定位
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的getResource方法:
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith("/")) {
return getResourceByPath(location);
}
//處理帶有classPath標識的Resource
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
//處理URL資源
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
return getResourceByPath(location);
}
}
}
在getResource方法中我們可以清晰地看到Resource資源的定位。這裡可以清晰地看到getResourceByPath方法的調用,getResourceByPath方法的具體實現有子類來完成,在FileSystemXmlApplicationContext實現如下:
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
這樣代碼就回到了博客開初的FileSystemXmlApplicationContext 中來了,它提供了FileSystemResource 來完成從文件系統得到配置文件的資源定義。當然這僅僅只是Spring IoC容器定位資源的一種邏輯,我們可以根據這個步驟來查看Spring提供的各種資源的定位,如ClassPathResource、URLResource等等。下圖是ResourceLoader的繼承關系:
參考文獻
1、《Spring技術內幕 深入解析Spring架構與設計原理》--第二版
2、Spring:源碼解讀Spring IOC原理
3、【Spring】IOC核心源碼學習(二):容器初始化過程