【spring原始碼系列】之【xml解析】

小豬爸爸發表於2021-04-19

1. 讀原始碼的方法

java程式設計師都知道讀原始碼的重要性,尤其是spring的原始碼,程式碼設計不僅優雅,而且功能越來越強大,幾乎可以與很多開源框架整合,讓應用更易於專注業務領域開發。但是能把spring的原始碼吃透,不僅需要花費大量時間與精力,更需要需要掌握一些方法。下面結合自己讀原始碼與走過的一些彎路,結合網上知名部落格專家的建議,整理出以下要點,與讀者共勉。

1.1 重視官方英文文件

spring的官方文件寫的非常全面,基本可以認為是spring原始碼思想的一手來源,上面有很多例子不僅幫助讀者如何應用,更能幫助我們瞭解其背後的思想,官方也用大量描述性的文字進行了相關思想的解讀,讓讀者從一個總覽上看大致瞭解spring的核心功能與特性。截止到目前,官方的最新版本是5.3.6,地址如下:
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core

1.2 寫示例程式debug原始碼

通過簡單的示例程式,找到原始碼的入口,通過debug而非僅僅看靜態的原始碼,只有看到真實跑起來的程式以及執行時的值時,心裡才大致清楚原始碼做了哪些事情。

1.3 抓大放小

抓住原始碼主要流程,而非陷入細節,如果一開始就摳細節,不僅會打消看原始碼的積極性,也理不清主要流程,最後只能半途而廢。只有主流程非常熟悉的情況下,並有時間精力,有興趣可以深究一些自己感興趣的細節。

1.4 寫原始碼註釋、畫流程圖

原始碼的一些重要方法與主要流程可以通過寫註釋、畫流程圖來加深理解。

1.5 思考背後的設計思想

原始碼之所以經典,是因為設計思想優秀,spring的原始碼在設計模式的靈活應用、類的抽象與封裝、框架的可擴充套件性都做到了極致,可以把該思想以及實踐應用到自己的專案設計裡面。

1.6 螺旋式學習

任何知識都是循序漸進,原始碼學習更是如此,因為原始碼很容易讓人半途而廢,只有通過刻意重複來逐步提升,每一次都不求甚解,能搞懂多少就搞懂多少,但是每一次都比上一次的理解提升一點,也可參考優質部落格系列對比學習,最終將原始碼的精髓吃透。

2. xml檔案解析

2.1 流程概覽

上圖描述了xml的解析的主要流程,大致分為三個步驟:
step1: 建立BeanFactory物件;
step2: 解析xml標籤,預設標籤如beanimport,自定義標籤<context:component-scan base-package=xxx>,把標籤封裝成BeanDefinition物件;
step3: 最後通過序號產生器BeanDefinitionRegistry註冊到BeanFactory的實現類DefaultListableBeanFactory

2.2 原始碼分析

AbstractApplicationContext類中最重要的方法refresh(),裡面呼叫很多方法,本文先重點看xml解析相關的方法。

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {

		        ......

			/*
			  1、建立BeanFactory物件
			* 2、xml解析
			* 	預設標籤解析:bean、import等
			* 	自定義標籤解析 如:<context:component-scan base-package="com.xxx"/>
			* 	自定義標籤解析流程:
			* 		a、根據當前解析標籤的頭資訊找到對應的namespaceUri
			* 		b、載入spring所以jar中的spring.handlers檔案。並建立對映關係
			* 		c、根據namespaceUri從對映關係中找到對應的實現了NamespaceHandler介面的類
			* 		d、呼叫類的init方法,init方法是註冊了各種自定義標籤的解析類
			* 		e、根據namespaceUri找到對應的解析類,然後呼叫paser方法完成標籤解析
			*
			* 3、把解析出來的xml標籤封裝成BeanDefinition物件
			* */
			// 告訴子類重新整理內部beanFactory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
                        ......
}

進入obtainFreshBeanFactory()方法,到refreshBeanFactory(),該方法是個抽象方法,由具體子類實現。

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		// 重新整理beanFactory
		refreshBeanFactory();
                // 獲取beanFactory
		return getBeanFactory();
}

子類AbstractRefreshableApplicationContext實現refreshBeanFactory()方法;

protected final void refreshBeanFactory() throws BeansException {
		//如果BeanFactory不為空,則清除BeanFactory和裡面的例項
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			//建立beanFactory
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			//傳入beanFactory,並裝載BeanDefinition物件
			loadBeanDefinitions(beanFactory);
			this.beanFactory = beanFactory;
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
}

上面方法主要做了兩件事,一是建立beanFactory,二是beanFactory作為引數傳入,並負責裝載BeanDefinition物件。接下來進入loadBeanDefinitions(beanFactory)方法,該方法是個抽象方法,交給子類AbstractXmlApplicationContext去實現,子類方法如下。

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		//為給定的BeanFactory建立xml的解析器,這裡是一個委託模式
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		beanDefinitionReader.setEnvironment(this.getEnvironment());
		//這裡傳一個this進去,因為ApplicationContext是實現了ResourceLoader介面的
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		initBeanDefinitionReader(beanDefinitionReader);
		//傳入解析器,裝載BeanDefinitions
		loadBeanDefinitions(beanDefinitionReader);
	}

上述方法建立xml解析器XmlBeanDefinitionReader,並交由解析器完成BeanDefinitions的裝載。再次進入AbstractXmlApplicationContext類的loadBeanDefinitions方法。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		//獲取需要載入的xml配置檔案
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
                        // xml解析器完成裝載
			reader.loadBeanDefinitions(configLocations);
		}
	}

再次進入xml解析器的loadBeanDefinitions方法,

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
      ......		
      //把字串型別的xml檔案路徑,轉換成Resource物件
      Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
      // 傳入Resource物件裝載BeanDefinitions
      int count = loadBeanDefinitions(resources);
      ......

進入上述的loadBeanDefinitions(resources)方法;

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
			//把inputSource 封裝成Document檔案物件
			Document doc = doLoadDocument(inputSource, resource);

			//根據document物件,去註冊BeanDefinitions
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}

上述方法一是封裝Document檔案物件,二是用Document物件去註冊BeanDefinitions,隨後進入registerBeanDefinitions方法;

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		//建立BeanDefinitionDocumentReader
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();

		//並委託BeanDefinitionDocumentReader這個類進行document的解析
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
}

該方法建立BeanDefinitionDocumentReader物件,並委託其解析document,進入registerBeanDefinitions方法。

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		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 {
			delegate.parseCustomElement(root);
		}
}

上述方法主要完成預設標籤解析,與自定義標籤解析,預設標籤如同importaliasbeanbeans,自定義標籤比如<aop:aspectj-autoproxy />,預設標籤重點分析bean標籤的解析;

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		        //bean籤解析
			BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				//完成document到BeanDefinition物件轉換後,對BeanDefinition物件進行快取註冊
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			        ......
		}
	}

上述方法主要是bean標籤的解析,最後對BeanDefinition物件進行快取註冊,先分析解析;

public AbstractBeanDefinition parseBeanDefinitionElement(
			Element ele, String beanName, @Nullable BeanDefinition containingBean) {

		        ......
                        //建立BeanDefinition
			AbstractBeanDefinition bd = createBeanDefinition(className, parent);
                        //解析BeanDefinition裡面的屬性
			parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
			bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
                        //解析meta元素
			parseMetaElements(ele, bd);
                        //解析Lookup方法
			parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
                        //解析ReplacedMethod方法
			parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
                        //解析構造引數方法
			parseConstructorArgElements(ele, bd);
                        //解析屬性元素方法
			parsePropertyElements(ele, bd);
                        //解析Qualifier元素方法
			parseQualifierElements(ele, bd);

			bd.setResource(this.readerContext.getResource());
			bd.setSource(extractSource(ele));
                        // 最後返回beanDefinition
                        return bd;

再分析註冊BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry()),進入該方法,最後會進入DefaultListableBeanFactory類,

@Override
	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是否已經註冊
		BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
		if (existingDefinition != null) {
			if (!isAllowBeanDefinitionOverriding()) {
				throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
			}
			else if (existingDefinition.getRole() < beanDefinition.getRole()) {
				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
				if (logger.isInfoEnabled()) {
					logger.info("Overriding user-defined bean definition for bean '" + beanName +
							"' with a framework-generated bean definition: replacing [" +
							existingDefinition + "] with [" + beanDefinition + "]");
				}
			}
			else if (!beanDefinition.equals(existingDefinition)) {
				if (logger.isDebugEnabled()) {
					logger.debug("Overriding bean definition for bean '" + beanName +
							"' with a different definition: replacing [" + existingDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			else {
				if (logger.isTraceEnabled()) {
					logger.trace("Overriding bean definition for bean '" + beanName +
							"' with an equivalent definition: replacing [" + existingDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			if (hasBeanCreationStarted()) {
				// Cannot modify startup-time collection elements anymore (for stable iteration)
				synchronized (this.beanDefinitionMap) {
					this.beanDefinitionMap.put(beanName, beanDefinition);
					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					removeManualSingletonName(beanName);
				}
			}
			else {
				//把beanDefinition快取到map中
				this.beanDefinitionMap.put(beanName, beanDefinition);
				//把beanName放到beanDefinitionNames list中
				this.beanDefinitionNames.add(beanName);
				removeManualSingletonName(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}

最終看出所謂的註冊到BeanFactory的容器中的類,無非就是一個定義了ConcurrentHashMap型別的beanDefinitionMap

自定義標籤解析BeanDefinitionParserDelegate類,執行parseCustomElement方法;

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
                // 獲取namespaceURI
		String namespaceUri = getNamespaceURI(ele);
		if (namespaceUri == null) {
			return null;
		}
                // 解析namespaceURI對應的handler類
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
                // 執行handler的解析方法
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}

上述過程主要完成以下步驟:
step1:獲取namespaceURI;
step2:解析namespaceURI對應的handler類;
step3:執行handler方法解析。
其中step2又分為幾個步驟,程式碼進入如下方法

public NamespaceHandler resolve(String namespaceUri) {
                // 從spring.handler裡面獲取對映關係
		Map<String, Object> handlerMappings = getHandlerMappings();
                // 根據namespaceURI從對映關係map中獲取對應的處理類handler
		Object handlerOrClassName = handlerMappings.get(namespaceUri);
		if (handlerOrClassName == null) {
			return null;
		}
		else if (handlerOrClassName instanceof NamespaceHandler) {
			return (NamespaceHandler) handlerOrClassName;
		}
		else {
                        // 通過反射例項化namespaceHandler類
			String className = (String) handlerOrClassName;
			try {
				Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
				if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
					throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
							"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
				}
                                
                                // 例項化namespaceHandler物件
				NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
                                // 執行init方法
				namespaceHandler.init();
                                // 把新的namespaceUri與namespaceHandler對映關係組裝到map中
				handlerMappings.put(namespaceUri, namespaceHandler);
				return namespaceHandler;
			}

上述解析namespaceURI對應的handler類,對應步驟又課分為如下幾步,
step1:從spring.handler裡面獲取對映關係;
step2:根據namespaceURI從對映關係map中獲取對應的處理類handler;
step3:通過反射獲取handler物件,並執行init方法,完成自定義標籤註冊;

3. 總結

本文主要分析了xml標籤的解析,主要步驟與流程圖上述程式碼分析與時序圖,通過除錯可以清晰觀察到解析過程,後續將通過示例分析beanDefinition類的相關屬性。

相關文章