1. 程式人生 > >spring原始碼剖析(二)Spring預設標籤解析及註冊實現

spring原始碼剖析(二)Spring預設標籤解析及註冊實現

在使用spring的時候,我也經常會使用到bean標籤,beans標籤,import標籤,aop標籤等。

下面主要為讀者介紹spring的預設的自帶標籤的解析流程。

驗證模式(DTD&XSD)

dtd基本已被淘汰,現在spring的驗證模式基本都是採用xsd檔案作為xml文件的驗證模式,通過xsd檔案可以檢查該xml是否符合規範,是否有效。在使用xsd檔案對xml文件進行校驗的時候,除了要名稱空間外(xmlns="http://www.springframework.org/schema/beans")還必須指定該名稱空間所對應的xsd文件的存貯位置,通過schemaLocation屬性來指定名稱空間所對應的xsd文件的儲存位置,它包含兩部分,一部分是名稱空間的URI,另一部分是該名稱空間所標識的xsd檔案位置或URL地址(xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd“)。

名稱空間URI

當我們使用spring標籤的時候,例如使用aop標籤的時候,這個aop標籤是在哪裡解析的呢,例如看下面的例子:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:aop="http://www.springframework.org/schema/aop"  
	xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd  ">
        
 	<aop:aspectj-autoproxy/>  
    <bean name="purchaseService"   class="net.itaem.aop.PurchaseService" />
    <bean name="purchaseService2"   class="net.itaem.aop.fPurchaseService2" />
    <bean class="net.itaem.aop.LogAspect"/>    
</beans>
其中schemaLocation有包含http://www.springframework.org/schema/aop這個地址,我們可以在這裡找到 我們可以看到,它對應著一個類的路徑,是AopNamespaceHandler,著代表著aop的實現是經過這個處理器實現的。

XSD檔案位置

我們可以看到在xsi:schemaLocation裡面http://www.springframework.org/schema/aop 後面還跟著http://www.springframework.org/schema/aop/spring-aop-3.2.xsd,這個xsd檔案代表著aop標籤的定義以及解析校驗規範都是在這裡定義的。那麼是不是要去這個網址http://www.springframework.org/schema/aop/spring-aop-3.2.xsd下載呢,讓我們先看看下面這個截圖:


從中可以看到http://www.springframework.org/schema/aop/spring-aop-3.2.xsd對應著一個路徑,在這個路徑我們可以找到對應的xsd檔案,所以,在解析該標籤的時候spring會更具schemas檔案下面對應的路徑去找這個校驗檔案,如果實在是沒有找到的話它才回去網上下載然後再執行校驗。

確定XML的解析模式

spring的配置檔案已經寫好了,spring啟動的時候,它究竟是如何確定spring使用的是哪種xml驗證模式呢,是DTD還是XSD,下面我們先通過時序圖看看spring在判斷驗證模式的實現
圖1 繞了那麼多個方法,我們終於看到獲取xml驗證模式的獲取了,是不是覺得很蛋疼,說實話,要不是畫了時序圖我也不知道這繞了那麼遠。好了,讓我們先看看它的核心實現程式碼吧
	protected int getValidationModeForResource(Resource resource) {
		int validationModeToUse = getValidationMode();
//如果是手動指定了驗證模式,那麼就使用手動指定的驗證模式
		if (validationModeToUse != VALIDATION_AUTO) {
			return validationModeToUse;
		}
//如果未指定,那麼就自動檢測驗證模式
		int detectedMode = detectValidationMode(resource);
		if (detectedMode != VALIDATION_AUTO) {
			return detectedMode;
		}
		// Hmm, we didn't get a clear indication... Let's assume XSD,
		// since apparently no DTD declaration has been found up until
		// detection stopped (before finding the document's root tag).
		return VALIDATION_XSD;
	}
一般的話,我們是不會手動去指定spring的驗證模式的,所以的話通常是spring自動檢測xml的驗證模式,那麼通過什麼檢測,怎麼實現呢,detectValidationMode方法是怎麼判斷的呢,其實判斷很簡單,就是判斷xml檔案開始時候的定義是否包含文件型別定義(doctype),如果包含,那麼就是dtd的文件驗證模式,如果不包含,那麼就是xsd的驗證模式。

載入xml文件

確定好,文件的解析模式後,下面的話便呼叫loadDocument方法對xml文件進行載入。對於xml文件的載入spring採用的還是javax.xml.parsers.DocumentBuilderFactory下面的DocumentBuilder去解析inputsource,返回document物件。 那麼解析的時候是怎麼獲取驗證檔案的呢。在文章的前面有說過如果本地有驗證檔案那麼就從本地獲取驗證檔案,如果本地沒有驗證檔案才去網上下載驗證檔案,那麼這是怎麼實現的呢? 其實這關鍵就在於EntityResolver這個類。何為EntityResolver,官方是這樣解釋的:如果SAX應用程式需要實現自定義處理外部屍體,則必須實現此介面並使用setEntityResolver方法向SAX驅動器註冊一個例項,也就是說對於解析一個xmlSAX首先讀取該XML文件的宣告,根據宣告去找相應的DTD或者XSD定義,然後根據DTD或者XSD對文件進行一個驗證。 在spring裡面用DelegatingEntityResolver實現了EntityResolver類,並在解析的時候會根據要獲取的解析檔案URL先去本地找對應的驗證檔案,DTD檔案是在當前路徑下去找,XSD檔案統一是從META-INF/Spring.schemas檔案中去找對應的本都驗證檔案。

解析並註冊BeanDefinitions

讓我們繼續doLoadBeanDefinitions方法的執行,先看看解析註冊beandefinitions的時序圖
圖2 1)spring有4種不同的預設標籤,分別是import,alias,bean,beans,這四種標籤的解析統一是由parseDefaultElement方法完成。 2)自定義的標籤,像aop標籤,是由parseCustomElement方法完成的。

預設標籤解析

讓我們來先看看spring是如何解析預設的標籤的,先看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);
		}
		//對beans標籤的解析
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}
從上面可以看大parseDefaultElement方法包含了spring 4中不同的預設標籤的解析入口,針對這4種標籤的解析大同小異,但是其中最複雜的應該屬bean標籤,因為他的屬性最多,所邏輯也就比較複雜一點,下面我們通過bean標籤的解析流程來看看spring是如何解析預設標籤的。 先上DefaultBeanDefinitionDocumentReader類的processBeanDefinition方法:
/**
	 * Process the given bean element, parsing the bean definition
	 * and registering it with the registry.
	 */
	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// 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)首先委託BeanDefinitionParserDelegate類的parseBeanDefinitionElement方法進行元素解析,返回BeanDefinitionHolder型別的示例bdHolder,經過這個方法後,bdHolder示例已經包含我們配置檔案中配置的各個屬性了,如class、name、id、alias之類的屬性。 2)當返回的bdHolder不為空的情況下,若存在預設標籤的子節點下再有自定義屬性,還需要再次對自定義標籤進行解析,也就是呼叫decorateBeanDefinitionIfRequired方法 3)解析完成後,需要對解析之後的bdHolder進行註冊,同樣,註冊操作委託給了BeanDefinitionReaderUtils的registerBeanDefinition方法。 4)最後發出響應事件,通知相關的監聽器,這個bean已經載入完成了。 下面讓我看看parseBeanDefinitionElement方法的實現:
	/**
	 * Parses the supplied {@code <bean>} element. May return {@code null}
	 * if there were errors during parse. Errors are reported to the
	 * {@link org.springframework.beans.factory.parsing.ProblemReporter}.
	 */
	public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
		//獲取id屬性
		id = ele.getAttribute(ID_ATTRIBUTE);
		//獲取name屬性
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
		//分割name屬性
		List<String> aliases = new ArrayList<String>();
		if (StringUtils.hasLength(nameAttr)) {
			String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			aliases.addAll(Arrays.asList(nameArr));
		}

		String beanName = id;
		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);
		}
		//解析bean的屬性和元素,並分裝成GeneicBeanDefinition中(GeneicBeanDefinition是AbstractBeanDefinition實現類)
		AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
		if (beanDefinition != null) {
			if (!StringUtils.hasText(beanName)) {
			//如果不存在beanName,則使用預設的規則建立beanName
				try {
					if (containingBean != null) {
						beanName = BeanDefinitionReaderUtils.generateBeanName(
								beanDefinition, this.readerContext.getRegistry(), true);
					}
					else {
						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, beanName, aliasesArray);
		}

		return null;
	}

解析的核心邏輯由parseBeanDefinitionElement方法實現,讓我們看看parseBeanDefinitionElement方法到底為我們做了哪些操作。 1)提取元素中的id和name屬性 2)進一步解析bean中其他所有的屬性,這些屬性包含scope、singleton、abstract、lazy-init、autowire、dependency-check、depends-on、autowire-candidate、primary、init-method、destroy-method、factory-method、factory-bean。並且解析bean下面所有的元素,其中包含:meta、lookup-method、replace-method、constructor-arg、property、qualifier。解析完成這些屬性和元素之後(元素和屬性很多,所以這是一個龐大的工作量),統一封裝到GeneicBeanDefinition型別的例項中。 3)如果檢測到bean沒有指定的beanName,那麼便使用預設的規則為bean生成一個beanName。 4)將獲取到的資訊封裝到BeanDeinitionHolder裡面去。

自定義標籤的解析

關於自定義標籤的解析流程的話我的部落格也有介紹,這裡就不重複介紹了,想溫習一下的朋友可以去看看,spring原始碼剖析(四)自定義標籤解析流程

註冊BeanDefinition

讓我們回到processBeanDefinition方法,找到beanDefinition的註冊的方法BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());讓我們來看看裡面的詳細實現:
	/**
	 * Register the given bean definition with the given bean factory.
	 * @param definitionHolder the bean definition including name and aliases
	 * @param registry the bean factory to register with
	 * @throws BeanDefinitionStoreException if registration failed
	 */
	public static void registerBeanDefinition(
			BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
			throws BeanDefinitionStoreException { 

		// Register bean definition under primary name.
		String beanName = definitionHolder.getBeanName();
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// Register aliases for bean name, if any.
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String aliase : aliases) {
				registry.registerAlias(beanName, aliase);
			}
		}
	}

通過上面可以看出,解完之後的beandefinition都會被註冊到BeanDefinitionRegistry裡面去,其中BeanDefinition的註冊分兩部分,一種是通過beanName註冊,一種是通過別名aliase註冊。

通過beanName註冊BeanDefinition

通過beanName註冊BeanDefinition Spring究竟做了哪些事情呢,讓我們來看看:
	//---------------------------------------------------------------------
	// Implementation of BeanDefinitionRegistry interface
	//---------------------------------------------------------------------

	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {

		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}

		BeanDefinition oldBeanDefinition;

		synchronized (this.beanDefinitionMap) {
			oldBeanDefinition = this.beanDefinitionMap.get(beanName);
			if (oldBeanDefinition != null) {
				if (!this.allowBeanDefinitionOverriding) {
					throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
							"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
							"': There is already [" + oldBeanDefinition + "] bound.");
				}
				else {
					if (this.logger.isInfoEnabled()) {
						this.logger.info("Overriding bean definition for bean '" + beanName +
								"': replacing [" + oldBeanDefinition + "] with [" + beanDefinition + "]");
					}
				}
			}
			else {
				this.beanDefinitionNames.add(beanName);
				this.frozenBeanDefinitionNames = null;
			}
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}

		if (oldBeanDefinition != null || containsSingleton(beanName)) {
			resetBeanDefinition(beanName);
		}
	}

1)對AbstractBeanDefinition進行校驗。在解析XML檔案的時候我們進行過校驗,之前是對XML格式的校驗,現在做的校驗是對AbstractBeanDefinition的MethodOverrides的校驗 2)對beanName已經註冊的情況做處理,如果設定了不允許覆蓋bean,那麼會直接丟擲異常,否則是直接覆蓋 3)加入map快取 4)清楚解析之前留下的beanName的快取。

通過別名alias註冊BeanDefinition

首先我們看具體的程式碼實現:
public void registerAlias(String name, String alias) {
		Assert.hasText(name, "'name' must not be empty");
		Assert.hasText(alias, "'alias' must not be empty");
		if (alias.equals(name)) {
			this.aliasMap.remove(alias);
		}
		else {
			if (!allowAliasOverriding()) {
				String registeredName = this.aliasMap.get(alias);
				if (registeredName != null && !registeredName.equals(name)) {
					throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
							name + "': It is already registered for name '" + registeredName + "'.");
				}
			}
			checkForAliasCircle(name, alias);
			this.aliasMap.put(alias, name);
		}
	}

1)alias和beanName名字一樣的情況處理,如果是名字一樣,那麼直接刪除別名。 2)alias覆蓋處理。若aliasName已經指向了另一beanName則需要使用者的設定進行處理。 3)alias迴圈檢查。當A->B存在時候,如果出現了A->C->B那麼直接丟擲異常。 4)註冊alias

通知監聽器解析註冊已完成

讓我們回顧下processBeanDefinition方法,裡面有說到,當bean註冊完成之後便會呼叫getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));這裡便提供了途徑給開發人員對bean完成註冊之後進行相應的拓展,目前Spring裡面是空實現。

總結

上面只對spring的bean標籤的解析做了說明,其他的預設標籤,像import,alias,beans標籤,其實解析的方式大同小異,如果想探個究竟的朋友也可以跟著程式原始碼走上幾遍。 spring預設的標籤的解析及註冊的大體邏輯就這樣,其實認真閱讀過原始碼的同學,很容易理解裡面的邏輯,看起來複雜,其實也不是很難,不過有一點可以認同的是,spring的原始碼寫的的確很嚴謹,值得我們去學習。