-
前言
1.上一篇我們說到了refresh()方法,這個是容器初始化的入口。我們知道,容器初始化共有三個階段:Resource定位,BeanDefinition解析,BeanDefinition註冊。今天我們要看的是Resource定位。
2.重要提示:這三個過程是緊密聯繫的,可以說講解了Resource定位的源碼,其他兩個過程的源碼都出來了。所以我們今天只看Resource定位,對於其他不多做研究。因爲:先走通主線,再關注細節。
3.理解下組合複用的設計模式。舉例:ApplicationContext接口繼承了ListableBeanFactory,HierarchicalBeanFactory等等接口,那麼就意味着 ApplicationContext可以提供這些接口中定義的所有功能,但是這些功能的實際實現並不是由ApplicationContext的實現類提供的,而是以組合複用的方式委託給了各個接口的實際實現類來完成。 -
BeanDefinition的Resource定位
接着上一篇的refresh()方法,這裏是進入了AbstractApplicationContext的refresh()方法。(大家理解下組合複用,這裏我理解的也不是很清楚)。我們來看下這個refresh()方法,這個方法很長,大家先只關心關鍵代碼。
代碼1.1:AbstractApplicationContext的refresh()方法public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//調用容器準備刷新的方法,獲取 容器的當時時間,同時給容器設置同步標識.
prepareRefresh();
//告訴子類啓動refreshBeanFactory()方法,
//Bean定義資源文件的載入從子類的refreshBeanFactory()方法啓動【重點】
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//爲BeanFactory配置容器特性,例如類加載器、事件處理器等
prepareBeanFactory(beanFactory);
try {
//爲容器的某些子類指定特殊的BeanPost事件處理器
postProcessBeanFactory(beanFactory);
//調用所有註冊的BeanFactoryPostProcessor的Bean
invokeBeanFactoryPostProcessors(beanFactory);
//爲BeanFactory註冊BeanPost事件處理器.BeanPostProcessor是Bean後置處理器,用於監聽容器觸發的事件
registerBeanPostProcessors(beanFactory);
//初始化信息源,和國際化相關.
initMessageSource();
//初始化容器事件傳播器.
initApplicationEventMulticaster();
//調用子類的某些特殊Bean初始化方法
onRefresh();
//爲事件傳播器註冊事件監聽器.
registerListeners();
//初始化所有剩餘的單態Bean.
finishBeanFactoryInitialization(beanFactory);
//初始化容器的生命週期事件處理器,併發布容器的生命週期事件
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
上面這個方法是整個IOC容器的初始化全過程,那麼我們先關注obtainFreshBeanFactory()這個方法,因爲這個方法裏面會實現我們上述的Resource定位及其他兩個過程。來看下這個方法的源碼:
代碼1.2:AbstractApplicationContext的obtainFreshBeanFactory()方法protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
上面代碼可以看到refreshBeanFactory,AbstractApplicationContext類中只抽象定義了refreshBeanFactory()方法,容器真正調用的是其子類AbstractRefreshableApplicationContext實現的 refreshBeanFactory()方法,方法的源碼如下:
代碼1.3:AbstractRefreshableApplicationContext的refreshBeanFactory()方法protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {//如果已經有容器,銷 毀 容器中的bean,關閉容器
destroyBeans();
closeBeanFactory();
}
try {
//創建IoC容器
DefaultListableBeanFactory beanFactory = createBeanFactory();
//對IoC容器進行定製化,如設置啓動參數,開啓註解的自動裝配等
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
//調用載入Bean定義的方法,主要這裏使用了一個委派模式,在當前類中只定義了抽象的loadBeanDefinitions方法,具體的實現調用子類容器
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
上面代碼註釋中已經說到了,具體的方法在子類中實現,我們先來看下到底有哪些子類,然後找到我們Web容器啓動走的路線:
從上面的圖中可以看到,由於我們這裏是通過Web啓動的,所以我們自然來到了XmlWebApplicationContext的loadBeanDefinitions方法,來看下代碼:代碼1.3:XmlWebApplicationContext的loadBeanDefinitions(DefaultListableBeanFactory beanFactory)方法@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 這裏使用XMLBeanDefinitionReader來載入bean定義信息的XML文件(後面再講)
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
//這裏配置reader的環境,其中ResourceLoader是我們用來定位bean定義信息資源位置的
///因爲上下文本身實現了ResourceLoader接口,所以可以直接把上下文作爲ResourceLoader傳遞給XmlBeanDefinitionReader
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);
//這裏轉到定義好的XmlBeanDefinitionReader中對載入bean信息進行處理
loadBeanDefinitions(beanDefinitionReader);
}
上面的XMLBeanDefinitionReader我們先不理睬,後面BeanDefinition解析的時候會講到這個類,我們直接看loadBeanDefinitions方法,這裏是具體載入bean信息,那麼要載入就要知道bean的定義文件在哪,所以Resource定位還要繼續跟下去,來看這個方法的源碼:
代碼1.4:XmlWebApplicationContext的loadBeanDefinitions(XmlBeanDefinitionReader reader)方法protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
//獲取configLocations,也就是配置文件路徑
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
看到這裏大家還記得之前我們的web.xml裏面配置的context-param嗎?還記得我們的ContextLoader裏面設置了wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM))嗎?這裏我們就取到了xml的位置。但是這裏我們並沒有看到我們一直在說的Resource,那麼繼續看代碼:經過了一次重載的方法,我們最終可以看到這個方法:
代碼1.5:AbstractBeanDefinitionReader的loadBeanDefinitions方法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的路徑模式進行解析(像我們在web.xml裏面有可能使用通配符等),得到需要的Resource集合,這些Resource集合指向了我們定義好的BeanDefinition的信息,可以是多個文件。
if (resourceLoader instanceof ResourcePatternResolver) {
//這裏處理我們在定義位置時使用的各種pattern,需要ResourcePatternResolver來完成
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 {
// 這裏通過ResourceLoader來完成位置定位
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;
}
}
那麼對於取得Resource的具體過程,((ResourcePatternResolver) resourceLoader).getResources(location)這個方法大家點開看看就可以知道,是PathMatchingResourcePatternResolver中的實現的,它具體裏面就是針對我們配置的是否以“classpath*:” 開頭分別處理。對於resourceLoader.getResource(location)方法,具體是交給繼承Resource的子類完成的。這裏不再不多了,就放一張Resource的代碼路徑圖吧:
-
Resource組件
Resource組件與ResourceLoader組件一起工作,將字符串格式指示的資源解析爲Resource對象。事實上ResourceLoader是Resource的工廠類, ResourceLoader的核心工作就是解析location。
location示例:”classpath:applicationContext.xml”,”classpath:applicationContext-.xml”,”file:/some/resource/path/myTemplate.txt”, - ”http://XX/resource/path/myTemplate.txt“ ResourceLoader根據所指示的前綴返回特定的Resource對象。看下Resource的結構圖: Resource的結構圖:
【Spring源碼--IOC容器的實現】(二)BeanDefinition的Resource定位
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.