1. 程式人生 > 程式設計 >探索SpringBoot-Spring原始碼之物件是如何註冊到IoC容器中的?(十一)

探索SpringBoot-Spring原始碼之物件是如何註冊到IoC容器中的?(十一)

前文回顧

之前探索SpringBoot系列也是到了探索SpringBoot-一起看看Spring原始碼之Resource(十)。之前有提到過Spring容器最重要的階段分為三個,分別是Bean的發現,讀取,註冊。今天我們來看看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);
	}
複製程式碼

在這個函式中,主要做了兩件事情。

  1. 獲取專門的解析類
  2. 給子類留下處理的空間
  3. 解析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));
		}
	}
複製程式碼

上述程式碼大致做了這麼幾件事情。

  1. 使用委託類解析元素為bdHolder,bdHolder元素中已經獲得瞭解析完成後的id,name配置資訊
  2. 如果bdHolder不為空,裝飾bdHolder。那麼什麼時候為空呢?看完下一步的解析,可以看到當解析有問題的時候,會返回null。這裡對解析失敗的情況下,不會終止整個過程,還是繼續解析下一個元素。
  3. 委託給BeanDefinitionReaderUtils註冊BeanDefinition到registry中
  4. 通知事件 每一個基本上都非常重要。我們一個一個來進行分析。進入到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;
	}
複製程式碼

上面函式主要做了下面幾個事情。

  1. 提取元素的idname屬性
  2. 進一步解析其他所有的屬性並統一封裝到BeanDefinition
  3. 如果沒有beanName,那麼預設生成一個beanName
  4. 將獲取的資訊封裝到BeanDefinitionHolder的例項中 在上面的程式碼註釋中,我稍微分析了下得到idname屬性的過程,然後讓我們繼續深入看一下第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中配置的元素是如何被解析的。

  1. 獲得className屬性值
  2. 例項化一個BeanDefinition並將相關的屬性賦值上去
  3. 解析各個子元素 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。關鍵因素是之後的classNameclassLoaderSpring在這裡又使用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之後,不再使用Rootchild了,而是使用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。