【簡記】Java Web 內幕——Spring原始碼(元件分析,BeanFactory原始碼,Bean建立之前)
本章內容:
- Bean元件、Context元件解析
- BeanFactory的建立
- 初始化Bean例項之前的操作
Bean元件解析
Spring Bean 的建立是典型的工廠模式, 它的頂級介面是BeanFactory。
Bean工廠的類層次關係圖:
4個介面,共同定義了Bean 的集合、Bean 之間的關係和Bean 的行為。
Bean定義的類層次關係圖:
Bean 的定義完整地描述了在Spring 的配置檔案中你定義的< /bean>節點中所有的資訊,包括各種子節點。
Context元件分析
ApplicationContext 繼承了BeanFactory,這也說明了Spring 容
器中執行的主體物件是Bean 。另外ApplicationContext 繼承了Re sourceLoader 介面,使得ApplicationContext 可以訪問到任何外部資源。
ApplicationContext 必須要完成以下幾件事情:
- 標識一個應用環境。
- 利用Bean Factory 建立Bean 物件。
- 儲存物件關係表。
- 能夠捕獲各種事件。
建立BeanFactory工廠
refresh方法(位於AbstractApplicationContext),整個Spring Bean載入的核心,構建了Bean關係網(也就是Bean Factory)
public void refresh() throws BeansException, IllegalStateException {
synchronized (this .startupShutdownMonitor) {
// 為重新整理準備新的context
prepareRefresh();
// 重新整理所有BeanFactory子容器
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//建立BeanFactory
prepareBeanFactory(beanFactory);
try {
//設定BeanFactoy的後置處理
postProcessBeanFactory(beanFactory);
//呼叫BeanFactory的後處理器,這些後處理器是在Bean定義中向容器註冊的
invokeBeanFactoryPostProcessors(beanFactory);
//註冊Bean的後處理器,在Bean建立過程中呼叫。
registerBeanPostProcessors(beanFactory);
//對上下文中的訊息源進行初始化
initMessageSource();
//初始化上下文中的事件機制
initApplicationEventMulticaster();
//初始化其他的特殊Bean
onRefresh();
//檢查監聽Bean並且將這些Bean向容器註冊
registerListeners();
//例項化所有的(non-lazy-init)單件
finishBeanFactoryInitialization(beanFactory);
//釋出容器事件,結束Refresh過程
finishRefresh();
}
catch (BeansException ex) {
//為防止Bean資源佔用,在異常處理中,銷燬已經在前面過程中生成的單件Bean
destroyBeans();
// 重置 'active'標誌
cancelRefresh(ex);
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
首先refresh()方法有幾點是值得我們學習的:
1、方法是加鎖的,這麼做的原因是避免多執行緒同時重新整理Spring上下文
2、儘管加鎖可以看到是針對整個方法體的,但是沒有在方法前加synchronized關鍵字,而使用了物件鎖startUpShutdownMonitor,這樣做有兩個好處:
(1)refresh()方法和close()方法都使用了startUpShutdownMonitor物件鎖加鎖,這就保證了在呼叫refresh()方法的時候無法呼叫close()方法,反之亦然,避免了衝突
(2)另外一個好處不在這個方法中體現,但是提一下,使用物件鎖可以減小了同步的範圍,只對不能併發的程式碼塊進行加鎖,提高了整體程式碼執行的效率
3、方法裡面使用了每個子方法定義了整個refresh()方法的流程,使得整個方法流程清晰易懂。
refresh方法主要包含了以下步驟:
( 1 )構建BeanFactory ,以便於產生所需的”演員” 。
( 2 )註冊可能感興趣的事件。
( 3 )建立Bean 例項物件。
( 4 )觸發被監聽的事件。
建立和配置BeanFactory的核心程式碼(在obtainFreshBeanFactory()方法中被呼叫,位於AbstractRefreshableApplicationContext類中)
protected final void refreshBeanFactory() throws BeansException {
//這裡判斷,如果已經建立了BeanFactory,則銷燬並關閉該BeanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
//這裡是建立並設定持有的DefaultListableBeanFactor的地方同時呼叫
//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 XML document for "
+ getDisplayName(), ex);
}
}
//這就是在上下文中建立DefaultListableBeanFactory的地方,而getInternalParentBeanFactory()
//的具體實現可以
//參看AbstractApplicationContext中的實現,會根據容器已有的雙親IoC容器的資訊來生成
// DefaultListableBeanFactory的雙親IoC容器
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(getInternalParentBeanFactory());
}
BeanDefinition的載入和解析(以AbstractXmlApplicationContext為例)
//這裡是使用BeanDefinitionReader載入Bean定義的地方,因為允許有多種載入方式,雖然用得
//最多的是XML定義的形式,這裡通過一個抽象函式把具體的實現委託給子類來完成
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
throws IOException, BeansException;
//以AbstractXmlApplicationContext為例
//這裡是實現loadBeanDefinitions的地方
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
//建立XmlBeanDefinitionReader,並通過回撥設定到BeanFactory中去,建立BeanFactory
//的過程可以參考上文對程式設計式使用IoC容器的相關分析,這裡和前面一樣,使用的也是
DefaultListableBeanFactory
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinition
Reader(beanFactory);
//這裡設定XmlBeanDefinitionReader,為XmlBeanDefinitionReader配
//ResourceLoader,因為DefaultResourceLoader是父類,所以this可以直接被使用
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//這是啟動Bean定義資訊載入的過程
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
接著就是loadBeanDefinitions呼叫的地方,首先得到BeanDefinition資訊的Resource定位,然後直接呼叫 XmlBeanDefinitionReader來讀取,具體的載入過程是委託給BeanDefinitionReader完成的。因為這裡的BeanDefinition是通過XML檔案定義的,所以這裡使用XmlBeanDefinitionReader來載入BeanDefinition到容器中。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
getConfigLocations會先去找父類中定義的預設配置檔案,如果沒有,則呼叫實現類中的getDefaultConfigLocations()方法
protected String[] getDefaultConfigLocations() {
if (getNamespace() != null) {
return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
}
else {
return new String[] {DEFAULT_CONFIG_LOCATION};
}
}
上面的DEFAULT_CONFIG_LOCATION就是/WEB-INF/applicationContext.xml
public int loadBeanDefinitions(String location, Set 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的路徑模式進行解析,比如我們設定的各種Ant格式的路徑定義,得到需要的
//Resource集合,這些Resource集合指向我們已經定義好的BeanDefinition資訊,可以是多個檔案
if (resourceLoader instanceof ResourcePatternResolver) {
try {
//呼叫DefaultResourceLoader的getResource完成具體的Resource定位
Resource[] resources = ((ResourcePatternResolver) resourceLoader).
getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (int i = 0; i < resources.length; i++) {
actualResources.add(resources[i]);
}
}
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的getResource完成具體的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;
}
}
Resource的定位
//對於取得Resource的具體過程,我們可以看看DefaultResourceLoader是怎樣完成的
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
//這裡處理帶有classpath標識的Resource
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) {
//如果既不是classpath,也不是URL標識的Resource定位,則把getResource的
//重任交給getResourceByPath,這個方法是一個protected方法,預設的實現是得到
//一個ClassPathContextResource,這個方法常常會用子類來實現
return getResourceByPath(location);
}
}
}
getResourceByPath會被子類FileSystemXmlApplicationContext實現,這個方法返回的是一個 FileSystemResource物件,通過這個物件,Spring可以進行相關的I/O操作,完成BeanDefinition的定位。
Spring的BeanDefinion是怎樣按照Spring的Bean語義要求進行解析並轉化為容器內部資料結構的?
這個過程是在registerBeanDefinitions(doc, resource)中完成的。具體的過程是由BeanDefinitionDocumentReader來完成的,這個registerBeanDefinition還對載入的Bean的數量進行了統計。
具體的Spring BeanDefinition的解析是在BeanDefinitionParserDelegate(是一個類)中完成的。這個類裡包含了對各種Spring Bean定義規則的處理。解析完成以後,會把解析結果放到BeanDefinition物件中並設定到BeanDefinitionHolder中去。
beanClass、description、lazyInit這些屬性都是在配置bean時經常碰到的,都集中在這裡。這個BeanDefinition是IoC容器體系中非常重要的核心資料結構。通過解析以後,這些資料已經做好在IoC容器裡大顯身手的準備了。
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
//這裡只讀取定義的<bean>中設定的class名字,然後載入到BeanDefinition中去,只是做個
//記錄,並不涉及物件的例項化過程,物件的例項化實際上是在依賴注入時完成的
String className = null;
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
try {
String parent = null;
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
//這裡生成需要的BeanDefinition物件,為Bean定義資訊的載入做準備
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//這裡對當前的Bean元素進行屬性解析,並設定description的資訊
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele,
DESCRIPTION_ELEMENT));
//從名字可以清楚地看到,這裡是對各種<bean>元素的資訊進行解析的地方
parseMetaElements(ele, bd);
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
//解析<bean>的建構函式設定
parseConstructorArgElements(ele, bd);
//解析<bean>的property設定
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
}
//下面這些異常是在配置Bean出現問題時經常會看到的,原來是在這裡丟擲的這些檢查是在
//createBeanDefinition時進行的,會檢查Bean的class設定是否正確,比如這個類是否能找到
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found", ele, ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found", ele, err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing", ele, ex);
}
finally {
this.parseState.pop();
}
return null;
}
BeanDefinition在IoC容器中的註冊
在DefaultListableBeanFactory中,是通過一個HashMap來持有載入的BeanDefinition的,這個HashMap的定義在DefaultListableBeanFactory中可以看到,如下所示。
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);
有用到synchronized關鍵字,當有Bean正在建立,對beanDefinitionMap加鎖,再進行bean定義寫入
完成了BeanDefinition的註冊,就完成了IoC容器的初始化過程。此時,在使用的IoC容器DefaultListableBeanFactory中已經建立了整個Bean的配置資訊,而且這些BeanDefinition已經可以被容器使用了,它們都在beanDefinitionMap裡被檢索和使用。容器的作用就是對這些資訊進行處理和維護。這些資訊是容器建立依賴反轉的基礎,有了這些基礎資料,下面我們看一下在IoC容器中,依賴注入是怎樣完成的。
建立bean例項之前的一些操作
PrepareBeanFactory方法:
如果自定義的bean中沒有名為”systemProperties”和”systemEnvironment”的Bean,則註冊兩個Bean,Key為”systemProperties”和”systemEnvironment”,Value為Map,這兩個Bean就是一些系統配置和系統環境資訊。
invokeBeanFactoryPostProcessors方法:
這個是整個Spring流程中非常重要的一部分,是Spring留給使用者的一個非常有用的擴充套件點,BeanPostProcessor介面針對的是每個Bean初始化前後做的操作而BeanFactoryPostProcessor介面針對的是所有Bean例項化前的操作,注意用詞,初始化只是例項化的一部分,表示的是呼叫Bean的初始化方法,BeanFactoryPostProcessor介面方法呼叫時機是任意一個自定義的Bean被反射生成出來前。
- 有一些選項在設定好後通常就不會去變更,而有一些選項可能得隨時調整,這時候如果能提供一個更簡潔的設定,提供一些常用選項在其中隨時更改,這樣的程式在使用時會更有彈性
- 一種情況是,XML定義檔中定義了一些低許可權程式使用人員可以設定的選項,然而高許可權的程式管理員可以透過屬性檔案的設定,來推翻低許可權程式使用人員的設定,以完成高許可權管理的統一性。
List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
List<String> orderedPostProcessorNames = new ArrayList<String>();
List<String> nonOrderedPostProcessorNames = new ArrayList<String>();
for (String ppName : postProcessorNames) {
if (processedBeans.contains(ppName)) {
// skip - already processed in first phase above
}
else if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
}
else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
}
這裡分出了三個List,表示開發者可以自定義BeanFactoryPostProcessor的呼叫順序,具體為呼叫順序為:
- 如果BeanFactoryPostProcessor實現了PriorityOrdered介面(PriorityOrdered介面是Ordered的子介面,沒有自己的介面方法定義,只是做一個標記,表示呼叫優先順序高於Ordered介面的子介面),是優先順序最高的呼叫,呼叫順序是按照介面方法getOrder()的實現,對返回的int值從小到大進行排序,進行呼叫
- 如果BeanFactoryPostProcessor實現了Ordered介面,是優先順序次高的呼叫,將在所有實現PriorityOrdered介面的BeanFactoryPostProcessor呼叫完畢之後,依據getOrder()的實現對返回的int值從小到大排序,進行呼叫
- 不實現Ordered介面的BeanFactoryPostProcessor在上面的BeanFactoryPostProcessor呼叫全部完畢之後進行呼叫,呼叫順序就是Bean定義的順序
registerBeanPostProcessors方法
整體程式碼思路和invokeBeanFactoryPostProcessors方法類似,但是這裡不會呼叫BeanPostProcessor介面的方法,而是把每一個BeanPostProcessor介面實現類例項化出來並按照順序放入一個List中,到時候按順序進行呼叫。
具體程式碼思路可以參考invokeBeanFactoryPostProcessors,這裡就根據程式碼總結一下BeanPostProcessor介面的呼叫順序:
- 優先呼叫PriorityOrdered介面的子介面,呼叫順序依照介面方法getOrder的返回值從小到大排序
- 其次呼叫Ordered介面的子介面,呼叫順序依照介面方法getOrder的返回值從小到大排序
- 接著按照BeanPostProcessor實現類在配置檔案中定義的順序進行呼叫
- 最後呼叫MergedBeanDefinitionPostProcessor介面的實現Bean,同樣按照在配置檔案中定義的順序進行呼叫
initMessageSource方法
initMessageSource方法用於初始化MessageSource,MessageSource是Spring定義的用於實現訪問國際化的介面
initApplicationEventMulticaster方法
初始化上下文事件廣播器
onRefresh方法
一個模板方法,重寫它的作用是新增特殊上下文重新整理的工作,在特殊Bean的初始化時、初始化之前被呼叫。
把loc 容器比作一個箱子,在這個箱子裡有若干個球的模子,可以用這些模子來造很多種不同的球,還有一個造這些球模的機器,這個機器可以產生球模。那麼它們的對應關係就是BeanFactory ,即那個造球模的機器;球模就是Bean ,而球模造出來的球就是Bean
的例項。前面所說的幾個擴充套件點又在什麼地方呢?
BeanFactoryPostProcessor 對應到當造球模被造出來時, 此時你將有機會對其做出適當的修正,也就是它可以幫你修改球模。
而InitializingBean 和DisposableBean 是在球模造球的開始和結束階段,你可以完成一些預備和掃尾工作。
BeanPostProcessor 可以讓你對球模造出來的球做出適當的修正。最後還有一個FactoryBean ,它可是一個神奇的球模。這個球模不是預先就定型的,而是由你來確定它的形狀。既然你可以確定這個球模型的形狀,那麼它造出來的球肯定就是你想要的球了,這樣在這個箱子裡面可以發現所有你想要的球。