SpringBoot原理解析(一)- 基于xml配置bean(Java解析xml文件)
文章目录
- SpringBoot原理解析(一)- 基于xml配置bean(Java解析xml文件)
-
- 1.DOM 和 SAX 介绍
- 2.前置准备
- 3.DOM实战
- 4.SAX实战
- 5.Spring解析XML文件
-
- 5.1.org.springframework.context.support.AbstractApplicationContext#refresh
- 5.2.org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory
- 5.3.org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)
- 5.4.org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.xml.XmlBeanDefinitionReader)
- 5.5.org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.Resource...)
- 5.6.org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)
- 5.7.org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadBeanDefinitions
- 5.8.org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions
- 5.9.org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#registerBeanDefinitions
- 5.10.org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
- 5.11.org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
- 6.总结
XML(eXtensible Markup Language)是一种标记语言,由SGML简化而来,是许多置标语言(例如XMLs/RDF/RDFs/OWL等)的元语言,用于描述和组织数据的结构。
在Spring项目中,XML(eXtensible Markup Language)被广泛用作配置文件格式,用于定义和配置Spring应用程序的各种组件和功能。通过XML配置,可以将Java对象声明为Spring管理的Bean,并指定其生命周期、依赖关系和其他属性。
本章不会解析spring源码,仅以spring的application.xml
为例,将你如何利用读取xml文件。
1.DOM 和 SAX 介绍
1.1.DOM(Document Object Model)
DOM解析器将整个XML文档加载到内存中,并将其表示为树状结构。这意味着可以从根节点开始访问和操作文档中的任何部分,操作简单且灵活。
优点:
- 完整性和准确性:DOM模型在加载XML文档时校验文档的合法性,确保文档符合XML规范。
- 具有层次结构:DOM模型将XML文档表示为具有层次结构的树,这使得解析和操作复杂的文档结构变得容易。可以通过导航和遍历节点来访问和操作文档中的数据。
- 操作灵活,支持增删改查:DOM提供了一组标准的API方法,可以轻松地创建、修改、删除和查询XML文档的节点和属性。这使得开发者可以根据需要对文档进行灵活的操作和更新。
缺点:
- 内存占用高:由于DOM将整个XML文档加载到内存中,对于较大的XML文档,它可能消耗大量的内存。如果XML文档特别大,可能会引发内存溢出的问题。
- 性能较差:由于DOM需要加载整个文档到内存中,因此解析大型的XML文档会导致性能下降。而且,对文档进行频繁的修改和更新操作也会影响性能。
- 不适合流式处理:由于DOM需要将整个文档加载到内存中,因此它不适合处理大型、较长的XML文档或流式XML数据。相比之下,SAX(Simple API for XML)解析器更适合流式处理。
- API复杂:DOM提供了一组庞大的API方法和属性,需要开发者熟悉和理解不同的节点类型、方法和属性,以正确地操作和处理XML文档。
1.2.SAX(Simple API for XML)
SAX 解析器基于事件驱动的,逐行读取 XML 文档并触发事件。
优点:
- 内存占用低:SAX解析器以事件驱动的方式逐行读取XML文档,并在解析过程中生成事件。相比于DOM解析器将整个文档加载到内存中,SAX解析器逐行读取XML文档,减少了内存的占用。
- 高性能:由于SAX解析器是事件驱动的,它避免了将整个文档加载到内存中的开销,因此能够更高效地处理大型和长文档。对于只需要读取XML文档而不需要修改的场景,SAX解析器通常具有更好的性能。
- 适合流式处理:SAX解析器逐行读取XML文档,使得它非常适合处理大型、较长的XML文档或流式XML数据。它可以通过触发事件来逐行处理数据,而无需将整个文档加载到内存中。
- 简单易用:SAX提供了一组简单的API,使得它相对于DOM更易学和使用。开发者只需要实现事件处理器接口,并根据具体需求来处理读取到的事件即可。
缺点:
- 无法直接修改:SAX解析器是只读的,一旦读取到XML文档的某一部分,就无法直接修改它。如果需要对文档进行修改,需要使用其他方式(如DOM)来操作。
- 难以处理复杂结构:由于SAX是基于事件的,开发者需要逐行处理XML文档中的数据。对于复杂的文档结构,可能需要编写复杂的逻辑来处理各种事件。
- 缺乏上下文:SAX解析器只在解析过程中提供有限的上下文信息,对于需要在整个文档中进行一些上下文相关的操作,可能需要额外的处理。
- 需要自定义解析逻辑:SAX解析器是基于事件驱动的,开发者需要自定义解析逻辑来处理读取到的事件。相比于DOM解析器提供的一组标准的API,SAX可能需要额外的编码工作。
2.前置准备
2.1.applicaton.xml
application.xml配置文件内容如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jack" name="jack" class="org.ahao.xml.test.pojo.People">
<property name="name" value="Jack"/>
</bean>
</beans>
2.2.Personl
public class People {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2.3.BeanDefinitionElement
public class BeanDefinitionElement {
private String id;
private String name;
private String clazz;
/**
* 属性集合
*/
private List<BeanProperty> properties;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClazz() {
return clazz;
}
public void setClazz(String clazz) {
this.clazz = clazz;
}
public List<BeanProperty> getProperties() {
return properties;
}
public void setProperties(List<BeanProperty> properties) {
this.properties = properties;
}
@Override
public String toString() {
return "BeanDefinitionElement{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", clazz='" + clazz + '\'' +
", properties=" + properties +
'}';
}
}
2.4.BeanProperty
public class BeanProperty {
private String name;
private String value;
private String ref;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getRef() {
return ref;
}
public void setRef(String ref) {
this.ref = ref;
}
@Override
public String toString() {
return "BeanProperty{" +
"name='" + name + '\'' +
", value='" + value + '\'' +
", ref='" + ref + '\'' +
'}';
}
}
3.DOM实战
3.1.Document接口
org.w3c.dom.Document
,表示整个XML文档,并提供了各种方法来操作XML文档的不同部分。以下是org.w3c.dom.Document
接口的一些常用方法及其作用:
getElementById(String id)
:根据给定的id值获取具有指定ID的元素节点。getElementsByTagName(String tagName)
:根据给定的标签名获取全部指定标签名的元素节点列表。getElementsByTagNameNS(String namespaceURI, String localName)
:根据给定的命名空间URI和本地名称获取全部指定标签名的元素节点列表。getDocumentElement()
:获取文档的根元素节点。createElement(String tagName)
:创建一个具有指定标签名的元素节点。createTextNode(String data)
:创建一个包含指定文本内容的文本节点。createAttribute(String name)
:创建一个具有指定名称的属性节点。createComment(String data)
:创建一个包含指定注释内容的注释节点。importNode(Node importedNode, boolean deep)
:将一个节点从另一个文档导入到当前文档,并返回导入的节点。appendChild(Node newChild)
:将指定的节点添加为当前文档的最后一个子节点。insertBefore(Node newChild, Node refChild)
:将指定的节点插入到给定参考节点之前的位置。removeChild(Node oldChild)
:从当前文档中移除指定的子节点。replaceChild(Node newChild, Node oldChild)
:将指定的新节点替换为当前文档中的给定旧节点。
3.2.Node接口
org.w3c.dom.Node
接口,用于表示XML文档中的任何类型节点。以下是org.w3c.dom.Node
接口的一些常用方法及其作用:
getNodeName()
:获取节点的名称。getNodeValue()
:获取节点的值,对于元素节点和文本节点,返回null。getParentNode()
:获取节点的父节点。getChildNodes()
:获取节点的所有子节点的列表。getFirstChild()
:获取节点的第一个子节点。getLastChild()
:获取节点的最后一个子节点。getNextSibling()
:获取节点的下一个兄弟节点。getPreviousSibling()
:获取节点的上一个兄弟节点。getNodeType()
:获取节点的类型,以整数形式返回,例如1表示元素节点,3表示文本节点,等等。getTextContent()
:获取节点及其所有后代节点的文本内容。hasAttributes()
:检查节点是否具有属性。hasChildNodes()
:检查节点是否有子节点。setNodeValue(String nodeValue)
:设置节点的值。appendChild(Node newChild)
:将指定的节点添加为当前节点的最后一个子节点。insertBefore(Node newChild, Node refChild)
:将指定的节点插入到给定参考节点之前的位置。removeChild(Node oldChild)
:删除当前节点的指定子节点。
3.3.Demo演示
public class DomTest {
public static void main(String[] args) {
List<BeanDefinitionElement> beanDefinitions = new ArrayList<>();
// 获取类路径下application.xml配置文件的绝对路径
String pathname = ResourceUtil.toAbsolutePath("application.xml");
// 1.创建DOM解析器工厂
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
try {
// 2.创建DOM解析器
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
// 3.读取XML文件并解析为Document对象
Document parse = documentBuilder.parse(new File(pathname));
// 4.获取根节点/元素
Element root = parse.getDocumentElement();
System.out.println("根元素:"+root);
// 5.获取根元素的子节点
NodeList childNodes = root.getChildNodes();
if (childNodes != null){
// 6.遍历根元素的子节点
for (int i = 0; i < childNodes.getLength(); i++) {
Node item = childNodes.item(i);
if ("bean".equals(item.getNodeName())){
// 创建BeanDefinitionElement
BeanDefinitionElement beanDefinitionElement = new BeanDefinitionElement();
beanDefinitions.add(beanDefinitionElement);
// 判断是否具有属性
if (item.hasAttributes()){
// 获取bean标签的属性集合
NamedNodeMap attributes = item.getAttributes();
// 获取name属性
Node name = attributes.getNamedItem("name");
beanDefinitionElement.setName(name.getNodeValue());
// 获取id属性
Node id = attributes.getNamedItem("id");
beanDefinitionElement.setId(id.getNodeValue());
// 获取class属性
Node clazz = attributes.getNamedItem("class");
beanDefinitionElement.setClazz(clazz.getNodeValue());
}
// 获取bean标签的子节点列表
NodeList beanChildNodes = item.getChildNodes();
if (beanChildNodes != null){
List<BeanProperty> properties = new ArrayList<>();
for (int j = 0; j < beanChildNodes.getLength(); j++) {
Node property = beanChildNodes.item(i);
if ("property".equals(property.getNodeName())){
// 创建BeanProperty
BeanProperty beanProperty = new BeanProperty();
properties.add(beanProperty);
if (property.hasAttributes()) {
// 获取property标签的属性集合
NamedNodeMap attributes = property.getAttributes();
Node name = attributes.getNamedItem("name");
beanProperty.setName(name.getNodeValue());
Node value = attributes.getNamedItem("value");
beanProperty.setValue(value.getNodeValue());
}
}
}
}
}
}
}
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (SAXException e) {
throw new RuntimeException(e);
}
System.out.println(beanDefinitions);
}
}
// 控制台输出
根元素:[beans: null]
[BeanDefinitionElement{
id='jack', name='jack', clazz='org.ahao.xml.test.pojo.People', properties=null}]
4.SAX实战
4.1.DefaultHandler接口
org.xml.sax.helpers.DefaultHandler
是SAX解析器的默认事件处理程序类。以下是org.xml.sax.helpers.DefaultHandle
接口的一些常用方法及其作用:
startDocument()
:在解析开始时调用,标志着文档的开始。endDocument()
:在解析结束时调用,标志着文档的结束。startElement(String uri, String localName, String qName, Attributes attributes)
:开始解析一个元素时调用。- uri: 元素的命名空间URI。
- localName: 元素的本地名称。
- qName: 元素的限定名称。
- attributes: 元素的属性列表。
endElement(String uri, String localName, String qName)
:解析一个元素的结束标签时调用。characters(char[] ch, int start, int length)
:解析元素内容时调用。- ch: 字符数组,包含元素文本内容。
- start: 字符数组的起始位置。
- length: 字符数组中的有效字符长度。
startPrefixMapping(String prefix, String uri)
:处理元素的命名空间映射时调用。endPrefixMapping(String prefix)
:处理元素的命名空间映射结束时调用。
4.2.BeanParserHandler
自定义的解析事件处理器
public class BeanParserHandler extends DefaultHandler {
/**
* 存储bean标签元素对象
*/
private List<BeanDefinitionElement> beanDefinitions = new ArrayList<>();
/**
* bean内部 property元素标签
*/
private List<BeanProperty> properties;
// 是否处于bean元素标签中
private boolean inBeanElement = false;
// 解析元素的开始标签
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
if (qName.equals("bean")){
BeanDefinitionElement curBeanElement = new BeanDefinitionElement();
// 获取id属性
String id = attributes.getValue("id");
curBeanElement.setId(id);
// 获取name属性
String name = attributes.getValue("name");
curBeanElement.setName(name);
// 获取class属性
String clazz = attributes.getValue("class");
curBeanElement.setClazz(clazz);
beanDefinitions.add(curBeanElement);
properties = new ArrayList<>();
curBeanElement.setProperties(properties);
inBeanElement = true;
}else if ("property".equals(qName)){
BeanProperty beanProperty = new BeanProperty();
// 获取name属性
String name = attributes.getValue("name");
beanProperty.setName(name);
// 获取value属性
String value = attributes.getValue("value");
beanProperty.setValue(value);
// 获取ref属性
String ref = attributes.getValue("ref");
beanProperty.setRef(ref);
properties.add(beanProperty);
}
}
// 解析元素的结束标签
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
if (inBeanElement && "bean".equals(qName)){
inBeanElement = false;
properties = new ArrayList<>();
}
}
// 获取BeanDefinitionElement列表
public List<BeanDefinitionElement> getBeanDefinitions() {
return beanDefinitions;
}
}
4.3.Demo演示
public class SAXTest {
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
// 1.获取文件绝对路径
String s = ResourceUtil.toAbsolutePath("application.xml");
// 2.创建SAX解析器工厂
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
// 3.创建SAX解析器实例
SAXParser saxParser = saxParserFactory.newSAXParser();
// 4.创建自定义的解析事件处理器
BeanParserHandler beanParserHandler = new BeanParserHandler();
// 5.解析XML文件
saxParser.parse(s,beanParserHandler);
// 获取解析后的BeanDefinitionElement列表
List<BeanDefinitionElement> beanDefinitions = beanParserHandler.getBeanDefinitions();
System.out.println(beanDefinitions);
}
}
// 控制台输出
[BeanDefinitionElement{
id='jack', name='jack', clazz='org.ahao.xml.test.pojo.People', properties=[BeanProperty{
name='name', value='Jack', ref='null'}]}]
5.Spring解析XML文件
以下为ClassPathXmlApplicationContext的启动流程中,读取Resource中有关BeanDefinition的加载过程
spring-boot:2.7.14 或者 spring-context:5.3.29
5.1.org.springframework.context.support.AbstractApplicationContext#refresh
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
this.prepareRefresh();
// this.obtainFreshBeanFactory()
// 创建一个新的DefaultListableBeanFactory实例作为BeanFactory,并进行必要的初始化和配置
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var10) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
}
this.destroyBeans();
this.cancelRefresh(var10);
throw var10;
} finally {
this.resetCommonCaches();
contextRefresh.end();
}
}
}
5.2.org.springframework.context.support.AbstractRefreshableApplicationContext#refreshBeanFactory
protected final void refreshBeanFactory() throws BeansException {
if (this.hasBeanFactory()) {
this.destroyBeans();
this.closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = this.createBeanFactory();
beanFactory.setSerializationId(this.getId());
this.customizeBeanFactory(beanFactory);
// 加载BeanDefinition
this.loadBeanDefinitions(beanFactory);
this.beanFactory = beanFactory;
} catch (IOException var2) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var2);
}
}
5.3.org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// XmlBeanDefinitionReader: 用于从XML配置文件中读取Bean的定义信息
// 在Spring框架中,Bean的定义信息通常是通过XML文件进行配置的。XmlBeanDefinitionReader类提供了一些方法,用于解析和读取XML文件中的Bean定义信息,并将其转换为Spring框架中的内部数据结构。
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
this.initBeanDefinitionReader(beanDefinitionReader);
// 加载BeanDefinition
this.loadBeanDefinitions(beanDefinitionReader);
}
5.4.org.springframework.context.support.AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.xml.XmlBeanDefinitionReader)
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// 用于获取配置文件的资源数组
Resource[] configResources = this.getConfigResources();
if (configResources != null) {
// 加载BeanDefinition(到了此处,才算开始解析)
reader.loadBeanDefinitions(configResources);
}
// 获取配置文件位置(字符串)
String[] configLocations = this.getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
5.5.org.springframework.beans.factory.support.AbstractBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.Resource…)
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int count = 0;
Resource[] var3 = resources;
int var4 = resources.length;
for(int var5 = 0; var5 < var4; ++var5) {
Resource resource = var3[var5];
// 加载BeanDefinition
count += this.loadBeanDefinitions((Resource)resource);
}
return count;
}
5.6.org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(org.springframework.core.io.support.EncodedResource)
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (this.logger.isTraceEnabled()) {
this.logger.trace("Loading XML bean definitions from " + encodedResource);
}
Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
} else {
int var6;
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
Throwable var4 = null;
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 执行从XML配置文件加载BeanDefinition的方法
var6 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} catch (Throwable var24) {
var4 = var24;
throw var24;
} finally {
if (inputStream != null) {
if (var4 != null) {
try {
inputStream.close();
} catch (Throwable var23) {
var4.addSuppressed(var23);
}
} else {
inputStream.close();
}
}
}
} catch (IOException var26) {
throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var26);
} finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
return var6;
}
}
5.7.org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadBeanDefinitions
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
// 此处的doc类型,就是上面DOM实战的org.w3c.dom.Document接口
// 就是解析XML配置文件为Document对象
Document doc = this.doLoadDocument(inputSource, resource);
//
int count = this.registerBeanDefinitions(doc, resource);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
} catch (BeanDefinitionStoreException var5) {
throw var5;
} catch (SAXParseException var6) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var6.getLineNumber() + " in XML document from " + resource + " is invalid", var6);
} catch (SAXException var7) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var7);
} catch (ParserConfigurationException var8) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var8);
} catch (IOException var9) {
throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var9);
} catch (Throwable var10) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var10);
}
}
5.8.org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 解析doc注册BeanDefinition
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
5.9.org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#registerBeanDefinitions
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
// 解析并注册BeanDefinition
doRegisterBeanDefinitions(doc.getDocumentElement());
}
5.10.org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
protected void doRegisterBeanDefinitions(Element root) {
// Any nested <beans> elements will cause recursion in this method. In
// order to propagate and preserve <beans> default-* attributes correctly,
// keep track of the current (parent) delegate, which may be null. Create
// the new (child) delegate with a reference to the parent for fallback purposes,
// then ultimately reset this.delegate back to its original (parent) reference.
// this behavior emulates a stack of delegates without actually necessitating one.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
// We cannot use Profiles.of(...) since profile expressions are not supported
// in XML config. See SPR-12458 for details.
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
preProcessXml(root);
// 解析并注册BeanDefinition
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
5.11.org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
public static final String NESTED_BEANS_ELEMENT = "beans";
// public static final String BEAN_ELEMENT = "bean";
public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;
public static final String ALIAS_ATTRIBUTE = "alias";
public static final String IMPORT_ELEMENT = "import";
// 此方法从配置文件根元素开始,逐个解析子元素。
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)) {
// 解析元素,并转换为Spring内部的数据结构然后注册到BeanFactory中
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
// 在该方法中,Spring会根据XML配置文件中的默认元素节点(比如<bean>,<alias>)来解析和创建对应的Bean定义。
// 该方法会根据元素节点的不同类型和属性进行解析和处理,如根据<bean>节点的属性来创建Bean实例,<alias>节点来为Bean定义添加别名。
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
6.总结
本篇博文讲解java中内置的两种解析XML文档的API,并分别演示使用方式。最后简单的过了一下Spring中关于解析XML配置文件,并注册BeanDefinition的相关源码。其实博主所演示的DOM案例,和Spring中最后解析注册BeanDefinition的原理是一样的,只不过Spring配置文件更加复杂,所以如果想深入了解具体的解析过程还需读者自己研究(其实这一部分内容并不重要,只需知道有这个过程即可)。