概念
在較早的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標籤
這個是最複雜的標籤,有茫茫多的屬性配置。所以我單獨用一篇文章來分析😂
2.1.2.解析自定義標籤
如果還不清楚如果在spring中使用自定義的標籤,可以看下這篇:https://blog.csdn.net/yuxiuzhiai/article/details/104175364
結語
在spring中,加載xml文件,着實是非常複雜了。。。
(水平有限,最近在看spring源碼,分享學習過程,希望對各位有點微小的幫助。如有錯誤,請指正~)