我們分析bean的注冊過程,就是下面這行代碼,他完成了配置文件的解析和bin的注冊功能,我們看看Spring到底為我們做了多少事情。
XmlBeanFactory bf = new XmlBeanFactory(res);
public class XmlBeanFactory extends DefaultListableBeanFactory {
//這里為容器定義了一個默認使用的bean定義讀取器
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
//在初始化函數(shù)中使用讀取器來對資源進行讀取,得到bean定義信息。
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
我們跟進去,發(fā)現(xiàn)他實際調(diào)用了XmlBeanDefinitionReader對象的loadBeanDefinitions方法。
3.1 XmlBeanDefinitionReader.loadBeanDefinitions
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//封裝資源文件
return loadBeanDefinitions(new EncodedResource(resource));
}
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
這個方法是整個資源加載的切入點,我們先大致看看這個方法的處理流程:
a. 封裝資源文件
new EncodedResource(resource)
b. 獲取輸入流
從EncodedResource對象中獲取InputStream并構造InputSource對象
c. 然后調(diào)用doLoadBeanDefinitions方法完成具體的加載過程
3.2 doLoadBeanDefinitions方法
int validationMode = getValidationModeForResource(resource);
Document doc = this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
return registerBeanDefinitions(doc, resource);
這個方法的代碼很長,如果不考慮異常處理,其實只做了三件事情:
a. 獲取對XML文件的驗證模式
b. 加載XML文件,并得到對應的Document對象
c. 根據(jù)返回的Document對象注冊bean信息
這里對驗證模式不進行討論;
這里不對Document對象的加載過程進行討論;
這里直接進入bean的注冊方法registerBeanDefinitions
3.3 registerBeanDefinitions方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 這里定義解析器,使用XmlBeanDefinitionParser來解析xml方式的bean定義文件 - 現(xiàn)在的版本不用這個解析器了,使用的是XmlBeanDefinitionReader
if (this.parserClass != null) {
XmlBeanDefinitionParser parser =
(XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass);
return parser.registerBeanDefinitions(this, doc, resource);
}
// 具體的注冊過程,首先得到XmlBeanDefinitionReader,來處理xml的bean定義文件
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getBeanFactory().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getBeanFactory().getBeanDefinitionCount() - countBefore;
}
當把文檔轉換為Document對象后,提取及注冊bean就是我們的重頭戲了。
這里并沒有看到我們想要的代碼,而是把工作委托給了BeanDefinitionDocumentReader對象去處理
3.4 BeanDefinitionDocumentReader.doRegisterBeanDefinitions方法
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
Element root = doc.getDocumentElement();
BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
preProcessXml(root);
parseBeanDefinitions(root, delegate);
postProcessXml(root);
}
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root.getNamespaceURI())) {
//這里得到xml文件的子節(jié)點,比如各個bean節(jié)點
NodeList nl = root.getChildNodes();
//這里對每個節(jié)點進行分析處理
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
String namespaceUri = ele.getNamespaceURI();
if (delegate.isDefaultNamespace(namespaceUri)) {
//這里是解析過程的調(diào)用,對缺省的元素進行分析比如bean元素
parseDefaultElement(ele, delegate);
}else {
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
經(jīng)過艱難險阻,山路十八彎,我們終于走到了核心邏輯的底部doRegisterBeanDefinitions,如果說以前一直是XML加載解析的準備階段,
那么這個方法算是真正地開始進行解析了,我們期待的核心部分真正開始了。
這個方法的代碼我們比較熟悉,讀取Document對象,循環(huán)每一個bean節(jié)點,然后進行處理。
Spring有兩類Bean,一個是默認的,一個是自定義的bean,這個方法對他們分別調(diào)用了不同方法進行處理。
3.5 對默認標簽的處理 processBeanDefinition方法
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
a. 首先利用委托類的parseBeanDefinitionElement方法進行元素解析,返回BeanDefinitionHolder對象bdHolder,經(jīng)過這個方法,bdHolder實例已經(jīng)包含我們配置文件中對bean的所有配置信息了,如name、class等。
b. 對bdHolder進行裝飾
c. 解析完成后,要對bdHolder進行注冊,同樣,注冊過程委托給了BeanDefinitionReaderUtils去處理
3.6 delegate.parseBeanDefinitionElement(ele)
這個方法便是對默認標簽解析的全過程了,他將一個element節(jié)點轉換成BeanDefinitionsHolder對象,其中ele和bdHolder中的屬性是對應的。不論是常用的還是不常用的我們都看到了,盡管有些復雜屬性還需要進一步解析,但絲毫不會影響我們興奮的心情。
a. 提取元素的id和name屬性
b. 進一步解析其他所有屬性并統(tǒng)一封裝到BeanDefinition類型的實例
c. 將獲取到的信息封裝到BeanDefinitionHolder實例中待續(xù)。。。
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);


AbstractBeanDefinition bd = createBeanDefinition(className, parent);
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
parseMetaElements(ele, bd);
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
parseConstructorArgElements(ele, bd);
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);
return new BeanDefinitionHolder(bd, beanName, aliasesArray); 跟進去,我們就看到解析的最底層了,如parseMetaElements,這里不再進行往下分析。
對于配置文件,解析也解析完了,裝飾也裝飾完了,已經(jīng)把xml中bean元素的各屬性封裝到了BeanDefinition對象,已經(jīng)可以滿足后續(xù)的使用要求了,剩下的工作便是注冊解析的BeanDefinition。
3.7 BeanDefinitionReaderUtils.registerBeanDefinition
這個方法并沒有做太多事情,而是直接調(diào)用了BeanDefinitionRegistry的注冊方法。BeanDefinitionRegistry是一個接口,有多個實現(xiàn)類,這里我們使用了默認的實現(xiàn)DefaultListableBeanFactory。
3.8 DefaultListableBeanFactory.registerBeanDefinition
代碼啰嗦了一大堆,實際上所謂的注冊,就是把beanName和beanDefinition對象作為鍵值對放到BeanFactory對象的beanDefinitionMap。
但Spring經(jīng)常把簡單的邏輯寫的非常“啰嗦”,仔細分析代碼,發(fā)現(xiàn)他完成了幾個事情:
a. 對bean對象的校驗
b. 檢查beanFactory中是否已經(jīng)有同名的bean,如果有,進行相應處理
c. 把bean對象放到beanDefinitionMap中(這就是最終所謂的注冊)
d. 清除整個過程緩存的對象數(shù)據(jù)
以上便是Spring對bean解析注冊的全過程,總結一下大致步驟:
1. 加載XML文件,封裝成Resource對象
2. 調(diào)用Reader對象方法讀取XML文件內(nèi)容,并將相關屬性放到BeanDefinition實例
3. 將BeanDefinition對象放到BeanFactory對象