spring5/springboot2源碼學習 -- xml文件的解析

概念

在較早的spring版本中,xml是配置spring唯一的方式。在如今的spring5.x版本已經spring boot2.x版本中,xml已經不再是唯一的配置手段了,甚至已經不再是推薦的手段。

但是,作爲spring元老級的功能,xml配置的方式在可預見的時間內還是不會被淘汰的。所以學習一下spring中讀取xml配置文件的方法也還是不錯的。

用法

演示一個十分基礎的用法,作爲講解原理的起點。

public class XmlBeanDefinitionTest {
  @Test
  public void test(){
    //創建一個實現了BeanDefinitionRegistry的BeanFactory實現
    //DefaultListableBeanFactory也是絕大多數場景下,BeanFactory的具體實現
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    //XmlBeanDefinitionReader創建,從名字可以看出來 這個類是用來從xml文件中讀取配置的
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
    //具體解析xml文件中的配置,並註冊到BeanDefinitionRegistry中去
    reader.loadBeanDefinitions(new ClassPathResource("xmlBeanDefinition.xml"));
  }
}

實現

進入reader.loadBeanDefinitions(new ClassPathResource(“xmlBeanDefinition.xml”))

可以發現主要的邏輯在XmlBeanDefinitionReader.doLoadBeanDefinitions()方法中

//省略異常捕獲代碼
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
  //將xml文件轉化爲Document對象
  Document doc = doLoadDocument(inputSource, resource);//@1
  //解析配置
  int count = registerBeanDefinitions(doc, resource);//@2
  if (logger.isDebugEnabled()) {
    logger.debug("Loaded " + count + " bean definitions from " + resource);
  }
  return count;
}

1.將xml文件轉換爲Document對象

對應上面@1處的方法

protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
  return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
                                          getValidationModeForResource(resource), isNamespaceAware());
}

EntityResolver是啥

可以當做是xml文件轉換爲對象的一個驗證模板,在spring 中有如下實現:

  • BeansDtdResolver:spring中以dtd驗證模式來加載xml中的中的配置。會從當前路徑下找spring-beans.dtd文件

  • PluggableSchemaResolver:spring中以scheme模式(xsd)加載配置。這個是可插入式的,spring中用的最多的是標籤。這個EntityResolver應用了SPI的手法,在使用時,會先到META-INF/spring.schemas路徑下讀取所有的配置項,每個配置項就是類似這種:

    http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans.xsd
    

    將一個xsd的url映射到本地的類路徑下

  • DelegatingEntityResolver:內部聚合了一個dtdResolver和一個schemaResolver,分別用來處理dtd模式和xsd模式的xml文件

  • ResourceEntityResolver:繼承自DelegatingEntityResolver,首先用DelegatingEntityResolver去讀取驗證文件,如果沒有讀取到,則在當前路徑下找

validationMode又是啥

validationMode就是上述的dtd或者xsd,是xml驗證的不同模式。在spring中,使用XmlValidationModeDetector來獲取驗證模式,檢測方法就是判斷驗證文件是否包含DOCTYPE,如果包含就是dtd,否則就是xsd

DocumentLoader.loadDocument()方法

public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
                             ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {

  DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
  if (logger.isTraceEnabled()) {
    logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
  }
  DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
  return builder.parse(inputSource);
}

spring中也是通過SAX解析XML文檔,項創建一個DocumentBuilderFactory,在創建出DocumentBuilder,然後去解析InputSource的文件,返回一個Document對象。具體知識都是xml的,跟spring無關,這裏就不再繼續介紹了

2.關鍵:將Document文檔轉化爲BeanDefinition,並註冊到BeanDefinitionRegistry中去

對應上面的@2方法

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
  //BeanDefinitionDocumentReader用於具體解析Document
  BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
  int countBefore = getRegistry().getBeanDefinitionCount();
  //解析並註冊BeanDefinition
  documentReader.registerBeanDefinitions(doc, createReaderContext(resource));//@2.1
  return getRegistry().getBeanDefinitionCount() - countBefore;
}

先看看這個createReaderContext()都幹了啥

public XmlReaderContext createReaderContext(Resource resource) {
   return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
         this.sourceExtractor, this, getNamespaceHandlerResolver());
}

這個NamespaceHandlerResolver又是啥

public interface NamespaceHandlerResolver {
  //將一個標籤的解析映射到一個NamespaceHandler
	NamespaceHandler resolve(String namespaceUri);
}

作用就是根據一個標籤找到一個具體的NamespaceHandler,由這個NamespaceHandler去具體解析標籤。其默認實現DefaultNamespaceHandlerResolver也使用了SPI機制,會去classpath低下找META-INF/spring.handlers文件。比如大dubbo的jar中就有這個文件:

http\://dubbo.apache.org/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

就是把標籤交給DubboNamespaceHandler去解析

2.1.DocumentReader.registerBeanDefinitions()方法

進入@2.1處的方法,然後兜兜轉轉,找到具體的邏輯處理方法:DocumentReader.doRegisterBeanDefinition()方法

然後再找到更具體的處理方法😂,DocumentReader.parseBeanDefinitions()

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)) {
            //@2.1.1解析默認標籤
						parseDefaultElement(ele, delegate);
					}
					else {
            //@2.1.2解析自定義標籤
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

轉來轉去,重要找到了,真的去處理xml裏面各個標籤的方法了。

解析默認標籤

@4的方法用於解析默認標籤。

主要的邏輯在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);
  }
  //解析嵌套的beans標籤
  else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
    // recurse
    doRegisterBeanDefinitions(ele);
  }
}
解析import標籤

一般使用如:

用於導入別的配置文件。其解析非常簡單:

  • 找到對應的文件
  • 調用前面的reader的loadBeanDefinitions方法
解析alias標籤

類似這樣

<bean id="userInfo" class="com.pk.study.spring.UserInfo"/>
<alias name="userInfo" alias="hahaha,aaa"/>

解析也很簡單,就是調用AliasRegistry接口的registerAlias方法註冊一下

解析bean標籤

這個是最複雜的標籤,有茫茫多的屬性配置。所以我單獨用一篇文章來分析😂

請見:xml文件的解析之bean標籤的解析

2.1.2.解析自定義標籤

如果還不清楚如果在spring中使用自定義的標籤,可以看下這篇:https://blog.csdn.net/yuxiuzhiai/article/details/104175364

結語

在spring中,加載xml文件,着實是非常複雜了。。。

(水平有限,最近在看spring源碼,分享學習過程,希望對各位有點微小的幫助。如有錯誤,請指正~)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章