探索SpringBoot-Spring原始碼之物件是如何註冊到IoC容器中的?(十一)
前文回顧
之前探索SpringBoot
系列也是到了探索SpringBoot-一起看看Spring原始碼之Resource(十)。之前有提到過Spring
容器最重要的階段分為三個,分別是Bean的發現,讀取,註冊。今天我們來看看Bean
的註冊。
- Bean的發現、讀取請看探索SpringBoot-一起看看Spring核心原始碼之refresh(九)
- Bean的註冊請往下看。
看完本文你將會知道Spring如何將Xml中的標籤轉化為BeanDefinition,實在是山路十八彎啊
註冊Bean
因為有了之前文章的鋪墊,直接看XmlBeanDefinitionReader#registerBenDefinitions
//對之前在準備工作中得到的Document和Resource進行解析和註冊Bean
public int registerBeanDefinitions(Document doc,Resource resource) throws BeanDefinitionStoreException{
//使用預設的DefaultBeanDefinitionDocumentReader來例項化BeanDefinitionDocumentReader
// Read document based on new BeanDefinitionDocumentReader SPI.
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//在初始化XmlBeanDefinitionReader的時候,會預設將BeanFactory傳遞進入成為BeanDefinitionRegistry
int countBefore = getRegistry().getBeanDefinitionCount();
//載入及註冊Bean
documentReader.registerBeanDefinitions(doc,createReaderContext(resource));
//記錄本次載入的BeanDefinition的個數
return getRegistry().getBeanDefinitionCount() - countBefore;
}
複製程式碼
經過了一系列的準備工作之後,我們終於進入到了實際載入和註冊Bean
的階段。讓我們繼續進入到DefaultDocumentBeanDefinitionDocumentReader#registerBeanDefinitions(Document,XmlReaderContext)
//進入到DefaultDocumentReader中對BeanDefinition進行解析
public void registerBeanDefinitions(Document doc,XmlReaderContext readerContext) {
//將readerContext上下文賦值給自己的屬性
this.readerContext = readerContext;
//列印一個日誌
logger.debug("Loading bean definitions");
//獲得root
Element root = doc.getDocumentElement();
//獲得專門的解析類
BeanDefinitionParserDelegate delegate = createHelper(readerContext,root);
//解析前處理,留給子類進行處理
preProcessXml(root);
parseBeanDefinitions(root,delegate);
//解析後處理,留給子類進行處理
postProcessXml(root);
}
複製程式碼
在這個函式中,主要做了兩件事情。
- 獲取專門的解析類
- 給子類留下處理的空間
- 解析root元素
稍微提一下,這個
preProcessXml(root)
和postProcessXml(root)
方法都留給子類進行實現,如果子類需要在處理過程的前面和後面需要做一些處理。對設計模式熟悉一些,那麼可以知道這裡使用了模板模式
。 繼續看看parseBeanDefinitions(Element,BeanDefinitionParserDelegate)
//解析root元素
protected void parseBeanDefinitions(Element root,BeanDefinitionParserDelegate delegate) {
//如果root是預設的元素
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
//判斷子元素是不是預設的元素
if (delegate.isDefaultNamespace(ele)) {
//解析預設的元素
parseDefaultElement(ele,delegate);
}
else {
//解析自定義的元素
delegate.parseCustomElement(ele);
}
}
}
}
else {
//如果root是定製的元素
delegate.parseCustomElement(root);
}
}
複製程式碼
在XML
配置裡面有兩大類宣告,一個是預設的如
<bean id="test" class="test.TestBean">
複製程式碼
另一類就是自定義的,如
<tx:annotation-driven/>
複製程式碼
Spring對於預設的標籤和自定義的標籤的解析有很大的不同,下面重點先解析一下預設的標籤。進入到DefaultBeanDefinitionDocumentReader#parseDefaultElement
中。
//解析預設的元素
private void parseDefaultElement(Element ele,BeanDefinitionParserDelegate delegate) {
//解析import元素
if (delegate.nodeNameEquals(ele,IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//解析alias元素
else if (delegate.nodeNameEquals(ele,ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//解析bean元素
else if (delegate.nodeNameEquals(ele,BEAN_ELEMENT)) {
processBeanDefinition(ele,delegate);
}
}
複製程式碼
我們先看bean
元素的解析,畢竟這是使用的最多的元素,也是IoC
容器的核心過程。讓我們進入到DefaultBeanDefinitionDocumentReader#processBeanDefinition(Element,BeanDefinitionParserDelegate)
protected void processBeanDefinition(Element ele,BeanDefinitionParserDelegate delegate) {
//使用委託類解析元素為bdHolder
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//裝飾bdHolder
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele,bdHolder);
try {
//委託給BeanDefinitionReaderUtils註冊BeanDefinition到registry中
// Register the final decorated instance.
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder,getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'",ele,ex);
}
// Send registration event.
//通知事件
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
複製程式碼
上述程式碼大致做了這麼幾件事情。
- 使用委託類解析元素為bdHolder,bdHolder元素中已經獲得瞭解析完成後的
id
,name
配置資訊 - 如果bdHolder不為空,裝飾bdHolder。那麼什麼時候為空呢?看完下一步的解析,可以看到當解析有問題的時候,會返回null。這裡對解析失敗的情況下,不會終止整個過程,還是繼續解析下一個元素。
- 委託給BeanDefinitionReaderUtils註冊BeanDefinition到registry中
- 通知事件
每一個基本上都非常重要。我們一個一個來進行分析。進入到
BeanDefinitionParserDelegate#parseBeanDefinitionElement(Element)
。
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) {
return parseBeanDefinitionElement(ele,null);
}
複製程式碼
繼續進入都函式內部。程式碼可能會有點長。但是,已經到了最關鍵的時候了,我們要有點耐心。有耐心,才能理解別人不能理解的東西。
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele,BeanDefinition containingBean) {
//獲取Id
String id = ele.getAttribute(ID_ATTRIBUTE);
//獲取name
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
//將name轉化放入到aliases的別名陣列中
List<String> aliases = new ArrayList<String>();
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr,BEAN_NAME_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
//id就是beanName
String beanName = id;
//對id為空的情況,做一些預設的補救措施。邏輯是aliase去掉第一個元素,將aliase的第一個元素作為beanName
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isDebugEnabled()) {
logger.debug("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
if (containingBean == null) {
checkNameUniqueness(beanName,aliases,ele);
}
//繼續深入解析該元素的其他屬性
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele,beanName,containingBean);
//不為null,表示正常解析
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition,this.readerContext.getRegistry(),true);
}
else {
//自己生成beanName
beanName = this.readerContext.generateBeanName(beanDefinition);
// Register an alias for the plain bean class name,if still possible,
// if the generator returned the class name plus a suffix.
// This is expected for Spring 1.2/2.0 backwards compatibility.
String beanClassName = beanDefinition.getBeanClassName();
if (beanClassName != null &&
beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() &&
!this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
aliases.add(beanClassName);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Neither XML 'id' nor 'name' specified - " +
"using generated bean name [" + beanName + "]");
}
}
catch (Exception ex) {
error(ex.getMessage(),ele);
return null;
}
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
return new BeanDefinitionHolder(beanDefinition,aliasesArray);
}
return null;
}
複製程式碼
上面函式主要做了下面幾個事情。
- 提取元素的
id
和name
屬性 - 進一步解析其他所有的屬性並統一封裝到
BeanDefinition
中 - 如果沒有
beanName
,那麼預設生成一個beanName
- 將獲取的資訊封裝到
BeanDefinitionHolder
的例項中 在上面的程式碼註釋中,我稍微分析了下得到id
和name
屬性的過程,然後讓我們繼續深入看一下第2步的函式去。讓我們看看到底是怎麼得到一個beanDefinition
的。
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele,String beanName,BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
//如果有class的值,那麼直接將className賦值
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物件
AbstractBeanDefinition bd = createBeanDefinition(className,parent);
//解析基本的BeanDefinition屬性
parseBeanDefinitionAttributes(ele,containingBean,bd);
//解析描述符
bd.setDescription(DomUtils.getChildElementValueByTagName(ele,DESCRIPTION_ELEMENT));
//解析meta元素
parseMetaElements(ele,bd);
//解析look-up子元素
parseLookupOverrideSubElements(ele,bd.getMethodOverrides());
//解析replaceMethod子元素
parseReplacedMethodSubElements(ele,bd.getMethodOverrides());
//解析構造器元素
parseConstructorArgElements(ele,bd);
//解析property元素
parsePropertyElements(ele,bd);
//解析Qulifier元素
parseQualifierElements(ele,bd);
//設定資源
bd.setResource(this.readerContext.getResource());
//設定source
bd.setSource(extractSource(ele));
return bd;
}
catch (ClassNotFoundException ex) {
error("Bean class [" + className + "] not found",ex);
}
catch (NoClassDefFoundError err) {
error("Class that bean class [" + className + "] depends on not found",err);
}
catch (Throwable ex) {
error("Unexpected failure during bean definition parsing",ex);
}
finally {
this.parseState.pop();
}
return null;
}
複製程式碼
可以看到上述程式碼好了好多事情,能夠清楚的看到自己在XML
中配置的元素是如何被解析的。
- 獲得
className
屬性值 - 例項化一個
BeanDefinition
並將相關的屬性賦值上去 - 解析各個子元素
BeanDefinition
是一個介面,定義了<bean>
標籤中所有屬性值的操作。可以認為BeanDifinition
是<bean>
標籤在Spring
中的抽象。Spring
將配置檔案中所有的bean
標籤都抽象為BeanDefinition
,然後註冊到BeanDefinitionRegistry
中。
我們繼續看看到底是怎麼初始化一個BeanDefinition
的例項的。
protected AbstractBeanDefinition createBeanDefinition(String className,String parentName)
throws ClassNotFoundException {
//可以看到需要的引數是parentName,className,classLoader
return BeanDefinitionReaderUtils.createBeanDefinition(
parentName,this.readerContext.getBeanClassLoader());
}
複製程式碼
可以看到需要的引數是parentName,classLoader
。關鍵因素是之後的className
和classLoader
。Spring
在這裡又使用BeanDefinitionReaderUtils
來例項化BeanDefinition
物件。我們繼續看BeanDefinitionReaderUtils#createBeandefinition()
。
public static AbstractBeanDefinition createBeanDefinition(
String parentName,String className,ClassLoader classLoader) throws ClassNotFoundException {
//還是使用的new關鍵字來對物件例項化,而且使用的是GenericBeanDefinition
GenericBeanDefinition bd = new GenericBeanDefinition();
//設定父節點名稱
bd.setParentName(parentName);
//只有在className不為空的時候,才使用classLoader來載入className
if (className != null) {
//還得判斷下classLoader是不是空,不為空,才載入
if (classLoader != null) {
bd.setBeanClass(ClassUtils.forName(className,classLoader));
}
else {
//如果有className就設定className
bd.setBeanClassName(className);
}
}
return bd;
}
複製程式碼
可以看到最關鍵的一點是還是使用的new
關鍵字來初始話GenericBeanDefinition
物件。可以在GenericBeanDefinition
的註釋上面看到,BeanDefinition
總共有三個實現類。但是,最後自從Spring2.5
之後,不再使用Root
和child
了,而是使用Generic
更加通用。
發現沒有,越過千山萬水,我們終於達到了真正解析bean
標籤的真正的屬性的函式了。讓我們揭開她的神祕的面紗。看一看BeanDelegete#parseBeanDefinitionAttributes
。
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele,BeanDefinition containingBean,AbstractBeanDefinition bd) {
//scope屬性
if (ele.hasAttribute(SCOPE_ATTRIBUTE)) {
// Spring 2.x "scope" attribute
bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));
if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
error("Specify either 'scope' or 'singleton',not both",ele);
}
}
//singleton屬性
else if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {
// Spring 1.x "singleton" attribute
bd.setScope(TRUE_VALUE.equals(ele.getAttribute(SINGLETON_ATTRIBUTE)) ?
BeanDefinition.SCOPE_SINGLETON : BeanDefinition.SCOPE_PROTOTYPE);
}
else if (containingBean != null) {
// Take default from containing bean in case of an inner bean definition.
bd.setScope(containingBean.getScope());
}
//abstract屬性
if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) {
bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));
}
//lazy_init屬性
String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE);
if (DEFAULT_VALUE.equals(lazyInit)) {
lazyInit = this.defaults.getLazyInit();
}
bd.setLazyInit(TRUE_VALUE.equals(lazyInit));
String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE);
bd.setAutowireMode(getAutowireMode(autowire));
String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);
bd.setDependencyCheck(getDependencyCheck(dependencyCheck));
if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) {
String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);
bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn,BEAN_NAME_DELIMITERS));
}
.........
return bd;
}
複製程式碼
可以看到經常被提到的scope
屬性,lazy-init
屬性,還是很多我們不太熟悉的屬性。這些屬性被不斷地放入到BeanDefinition
的例項中。
在將這些所有的屬性都再次一一解析之後,放入到BeanDefinition
中,至此,我們就完成了從XML
的<bean>
標籤到Spring
內部的BeanDefinition
的過程了。
在好好消化消化,告一個段落。
關於寫作
以後這裡每天都會寫一篇文章,題材不限,內容不限,字數不限。儘量把自己每天的思考都放入其中。
如果這篇文章給你帶來了一些幫助,可以動動手指點個贊,順便關注一波就更好了。
如果上面都沒有,那麼寫下讀完之後最想說的話?有效的反饋和你的鼓勵是對我最大的幫助。
另外打算把部落格給重新撿起來了。歡迎大家來訪問吃西瓜。
我是shane。今天是2019年8月24日。百天寫作計劃的第三十一天,31/100。