news 2026/4/15 20:58:47

Spring XML解析与BeanDefinition注册详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring XML解析与BeanDefinition注册详解

一、XML解析为BeanDefinition的时机

1.1 在Spring生命周期中的位置

XML解析为BeanDefinition发生在Spring容器启动阶段,具体时机如下:

Spring容器启动流程: 1. 创建BeanFactory 2. 【XML解析阶段】加载配置文件,解析XML,注册BeanDefinition ← 发生在这里 3. BeanFactoryPostProcessor处理 4. 实例化Bean 5. 依赖注入 6. 初始化Bean 7. 容器就绪

1.2 详细执行流程

ClassPathXmlApplicationContext为例:

// 1. 构造器调用publicClassPathXmlApplicationContext(StringconfigLocation){this(newString[]{configLocation},true,null);}// 2. refresh()方法 - Spring容器启动的核心方法publicvoidrefresh()throwsBeansException{synchronized(this.startupShutdownMonitor){// 准备刷新上下文prepareRefresh();// 【关键步骤】获取BeanFactory,在此过程中完成XML解析ConfigurableListableBeanFactorybeanFactory=obtainFreshBeanFactory();// 后续步骤...prepareBeanFactory(beanFactory);postProcessBeanFactory(beanFactory);invokeBeanFactoryPostProcessors(beanFactory);registerBeanPostProcessors(beanFactory);// ...}}// 3. obtainFreshBeanFactory() - 触发XML解析protectedConfigurableListableBeanFactoryobtainFreshBeanFactory(){refreshBeanFactory();// 在这里进行XML解析returngetBeanFactory();}// 4. refreshBeanFactory() - AbstractRefreshableApplicationContextprotectedfinalvoidrefreshBeanFactory(){// 创建BeanFactoryDefaultListableBeanFactorybeanFactory=createBeanFactory();// 【核心】加载BeanDefinitionloadBeanDefinitions(beanFactory);}// 5. loadBeanDefinitions() - AbstractXmlApplicationContextprotectedvoidloadBeanDefinitions(DefaultListableBeanFactorybeanFactory){// 创建XML的BeanDefinition读取器XmlBeanDefinitionReaderbeanDefinitionReader=newXmlBeanDefinitionReader(beanFactory);// 使用Reader读取XML并注册BeanDefinitionloadBeanDefinitions(beanDefinitionReader);}

1.3 时机总结

阶段说明
发生时机容器refresh()方法的obtainFreshBeanFactory()阶段
在Bean实例化之前此时只是注册元数据,Bean还未创建
在BeanFactoryPostProcessor之前BeanFactoryPostProcessor可以修改BeanDefinition
可修改性此阶段注册的BeanDefinition可被后续处理器修改

二、XML解析方式详解

Spring中主要有两种XML解析方式DOM解析SAX解析。Spring默认使用DOM解析。

2.1 DOM(Document Object Model)解析

2.1.1 基本原理
工作流程: XML文件 → 完整读入内存 → 构建DOM树 → 遍历节点 → 提取信息
2.1.2 Spring中的使用

Spring通过DocumentLoader加载XML为DOM Document:

// DefaultDocumentLoader.javapublicDocumentloadDocument(InputSourceinputSource,EntityResolverentityResolver,ErrorHandlererrorHandler,intvalidationMode,booleannamespaceAware){// 创建DocumentBuilderFactoryDocumentBuilderFactoryfactory=createDocumentBuilderFactory(validationMode,namespaceAware);// 创建DocumentBuilderDocumentBuilderbuilder=createDocumentBuilder(factory,entityResolver,errorHandler);// 解析为Document对象(DOM树)returnbuilder.parse(inputSource);}
2.1.3 解析过程
// 1. 加载DocumentDocumentdoc=documentLoader.loadDocument(...);// 2. 获取根元素Elementroot=doc.getDocumentElement();// 3. 遍历解析(以bean标签为例)NodeListnodeList=root.getElementsByTagName("bean");for(inti=0;i<nodeList.getLength();i++){ElementbeanElement=(Element)nodeList.item(i);// 4. 提取属性Stringid=beanElement.getAttribute("id");StringclassName=beanElement.getAttribute("class");Stringscope=beanElement.getAttribute("scope");// 5. 创建BeanDefinitionBeanDefinitionbd=newGenericBeanDefinition();bd.setBeanClassName(className);bd.setScope(scope);// 6. 解析子元素(property、constructor-arg等)parsePropertyElements(beanElement,bd);// 7. 注册到BeanFactoryregistry.registerBeanDefinition(id,bd);}
2.1.4 优势
优势说明
随机访问可以随意访问DOM树的任意节点
双向遍历可以从父节点到子节点,也可以从子节点到父节点
易于操作API简单直观,便于增删改查
支持修改可以修改DOM树的结构和内容
支持XPath可以使用XPath快速定位节点
2.1.5 劣势
劣势说明
内存占用大需要将整个XML加载到内存构建DOM树
解析速度慢大文件解析时需要较长时间
不适合大文件大型XML文件可能导致内存溢出
解析前必须完整必须等待整个文档加载完成
2.1.6 适用场景
  • ✅ Spring配置文件(通常较小,需要随机访问)
  • ✅ 需要频繁访问不同节点
  • ✅ 需要修改XML内容
  • ❌ 超大型XML文件(几百MB以上)

2.2 SAX(Simple API for XML)解析

2.2.1 基本原理
工作流程: XML文件 → 边读边解析 → 触发事件 → 事件处理器处理 → 不保留完整树结构

SAX采用事件驱动模型

// SAX事件处理器publicclassMyHandlerextendsDefaultHandler{// 1. 文档开始事件publicvoidstartDocument(){System.out.println("开始解析文档");}// 2. 元素开始事件publicvoidstartElement(Stringuri,StringlocalName,StringqName,Attributesattributes){if("bean".equals(qName)){Stringid=attributes.getValue("id");StringclassName=attributes.getValue("class");// 处理bean定义...}}// 3. 元素内容事件publicvoidcharacters(char[]ch,intstart,intlength){Stringcontent=newString(ch,start,length);// 处理文本内容...}// 4. 元素结束事件publicvoidendElement(Stringuri,StringlocalName,StringqName){if("bean".equals(qName)){// bean标签结束,注册BeanDefinition}}// 5. 文档结束事件publicvoidendDocument(){System.out.println("文档解析完成");}}
2.2.2 解析过程
// 1. 创建SAX解析器工厂SAXParserFactoryfactory=SAXParserFactory.newInstance();// 2. 创建解析器SAXParserparser=factory.newSAXParser();// 3. 创建事件处理器MyHandlerhandler=newMyHandler();// 4. 开始解析(触发事件)parser.parse(newFile("beans.xml"),handler);
2.2.3 事件触发顺序示例

对于以下XML:

<?xml version="1.0" encoding="UTF-8"?><beans><beanid="userService"class="com.example.UserService"><propertyname="name"value="test"/></bean></beans>

SAX解析触发的事件序列:

1. startDocument() 2. startElement("beans") 3. startElement("bean", {id="userService", class="com.example.UserService"}) 4. startElement("property", {name="name", value="test"}) 5. endElement("property") 6. endElement("bean") 7. endElement("beans") 8. endDocument()
2.2.4 优势
优势说明
内存占用小不需要加载整个文档到内存
解析速度快边读边解析,无需等待完整加载
适合大文件可以处理超大型XML文件
流式处理支持流式数据处理
2.2.5 劣势
劣势说明
单向解析只能从前往后解析,不能回退
无法随机访问不能直接访问特定节点
编程复杂需要手动维护状态,代码较复杂
不能修改无法修改XML内容
需要手动管理状态需要在事件处理器中维护上下文信息
2.2.6 适用场景
  • ✅ 超大型XML文件解析
  • ✅ 流式数据处理
  • ✅ 内存受限环境
  • ❌ 需要随机访问节点
  • ❌ 需要修改XML

2.3 其他解析方式

2.3.1 StAX(Streaming API for XML)

StAX是拉式解析(Pull Parsing),介于DOM和SAX之间:

XMLInputFactoryfactory=XMLInputFactory.newInstance();XMLStreamReaderreader=factory.createXMLStreamReader(newFileInputStream("beans.xml"));while(reader.hasNext()){intevent=reader.next();// 主动拉取事件if(event==XMLStreamConstants.START_ELEMENT){StringtagName=reader.getLocalName();if("bean".equals(tagName)){Stringid=reader.getAttributeValue(null,"id");StringclassName=reader.getAttributeValue(null,"class");// 处理...}}}

特点对比

特性SAXStAX
模型推式(事件驱动)拉式(迭代器模式)
控制解析器控制流程应用程序控制流程
易用性需要回调处理更直观,类似迭代器
性能略快略慢
2.3.2 JDOM 和 DOM4J

这些是第三方XML解析库,不是Spring默认使用的方式:

// DOM4J示例SAXReaderreader=newSAXReader();Documentdocument=reader.read(newFile("beans.xml"));Elementroot=document.getRootElement();for(Elementbean:root.elements("bean")){Stringid=bean.attributeValue("id");StringclassName=bean.attributeValue("class");// 处理...}

三、Spring为什么选择DOM解析

3.1 Spring的选择理由

理由说明
配置文件通常较小Spring配置文件一般不超过几MB,DOM的内存开销可接受
需要随机访问解析bean依赖关系需要随机访问不同节点
需要支持命名空间Spring支持自定义命名空间,DOM更容易处理
易于扩展DOM API更容易实现自定义标签解析
向后兼容历史原因,保持API稳定性

3.2 Spring的优化措施

虽然使用DOM,但Spring做了很多优化:

// 1. 延迟加载BeanDefinition// 只在第一次获取Bean时才解析// 2. 缓存解析结果// BeanDefinition注册后会缓存,避免重复解析// 3. 支持@Lazy注解// 延迟Bean的实例化// 4. 支持条件注册// @Conditional可以跳过不必要的Bean注册

四、DOM vs SAX 深度对比

4.1 技术对比表

对比维度DOMSAX
解析方式树形结构,全部加载事件驱动,边读边解析
内存占用高(与文件大小成正比)低(固定大小)
解析速度慢(需要构建完整树)快(流式处理)
访问方式随机访问顺序访问
遍历方向双向单向(前向)
修改能力支持不支持
API复杂度简单直观复杂(需要状态管理)
适用场景中小型文件、需要修改大型文件、流式处理
XPath支持支持不支持
线程安全不安全(需要同步)每个解析器独立使用

4.2 性能对比

以10MB的XML文件为例:

DOM解析: - 内存占用:约40-50MB(4-5倍文件大小) - 解析时间:约2-3秒 - 优势:解析后访问快速 SAX解析: - 内存占用:约5-10MB(固定开销) - 解析时间:约0.5-1秒 - 优势:内存友好,适合大文件

4.3 代码复杂度对比

DOM解析代码(简洁)

Documentdoc=builder.parse(xmlFile);NodeListbeans=doc.getElementsByTagName("bean");for(inti=0;i<beans.getLength();i++){Elementbean=(Element)beans.item(i);Stringid=bean.getAttribute("id");// 直接访问,逻辑清晰}

SAX解析代码(复杂)

publicclassBeanHandlerextendsDefaultHandler{privateStack<String>elementStack=newStack<>();privateBeanDefinitioncurrentBean;publicvoidstartElement(Stringuri,StringlocalName,StringqName,Attributesattributes){elementStack.push(qName);if("bean".equals(qName)){currentBean=newBeanDefinition();currentBean.setId(attributes.getValue("id"));}elseif("property".equals(qName)&&isInBean()){// 需要手动维护状态}}privatebooleanisInBean(){returnelementStack.contains("bean");}// 需要更多状态管理代码...}

五、Spring XML解析核心类

5.1 核心类图

XmlBeanDefinitionReader(XML读取器) ├── DocumentLoader(文档加载器) │ └── DefaultDocumentLoader(默认实现,使用DOM) │ ├── BeanDefinitionDocumentReader(文档解析器) │ └── DefaultBeanDefinitionDocumentReader │ ├── parseBeanDefinitions() - 解析beans标签 │ └── processBeanDefinition() - 解析bean标签 │ └── BeanDefinitionParserDelegate(解析委托) ├── parseBeanDefinitionElement() - 解析bean元素 ├── parsePropertyElements() - 解析property元素 └── parseConstructorArgElements() - 解析constructor-arg元素

5.2 关键方法调用链

// 1. XmlBeanDefinitionReader.loadBeanDefinitions()publicintloadBeanDefinitions(Resourceresource){returnloadBeanDefinitions(newEncodedResource(resource));}// 2. doLoadBeanDefinitions()protectedintdoLoadBeanDefinitions(InputSourceinputSource,Resourceresource){// 加载Document(使用DOM)Documentdoc=doLoadDocument(inputSource,resource);// 注册BeanDefinitionreturnregisterBeanDefinitions(doc,resource);}// 3. registerBeanDefinitions()publicintregisterBeanDefinitions(Documentdoc,Resourceresource){BeanDefinitionDocumentReaderdocumentReader=createBeanDefinitionDocumentReader();// 委托给DocumentReader处理documentReader.registerBeanDefinitions(doc,createReaderContext(resource));}// 4. DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions()protectedvoiddoRegisterBeanDefinitions(Elementroot){BeanDefinitionParserDelegateparent=this.delegate;this.delegate=createDelegate(getReaderContext(),root,parent);// 解析beans标签parseBeanDefinitions(root,this.delegate);}// 5. parseBeanDefinitions() - 遍历子元素protectedvoidparseBeanDefinitions(Elementroot,BeanDefinitionParserDelegatedelegate){NodeListnl=root.getChildNodes();for(inti=0;i<nl.getLength();i++){Nodenode=nl.item(i);if(nodeinstanceofElement){Elementele=(Element)node;if(delegate.isDefaultNamespace(ele)){// 默认命名空间(bean、import、alias等)parseDefaultElement(ele,delegate);}else{// 自定义命名空间(aop、tx、context等)delegate.parseCustomElement(ele);}}}}

六、实际应用建议

6.1 Spring配置文件优化

<!-- ❌ 避免:单个超大配置文件 --><beans><!-- 上千个bean定义... --></beans><!-- ✅ 推荐:拆分为多个模块 --><beans><importresource="spring-dao.xml"/><importresource="spring-service.xml"/><importresource="spring-web.xml"/></beans>

6.2 何时考虑SAX

如果需要处理超大型XML(非Spring配置场景):

// 场景:处理1GB的数据导出XMLpublicclassLargeXmlProcessorextendsDefaultHandler{privateintrecordCount=0;@OverridepublicvoidstartElement(Stringuri,StringlocalName,StringqName,Attributesattributes){if("record".equals(qName)){// 处理单条记录processRecord(attributes);recordCount++;if(recordCount%10000==0){System.out.println("Processed "+recordCount+" records");}}}privatevoidprocessRecord(Attributesattributes){// 处理并立即释放,不保留在内存}}

6.3 现代Spring项目建议

// 优先使用注解配置,避免XML@Configuration@ComponentScan("com.example")publicclassAppConfig{@BeanpublicUserServiceuserService(){returnnewUserServiceImpl();}}// 或使用Spring Boot@SpringBootApplicationpublicclassApplication{publicstaticvoidmain(String[]args){SpringApplication.run(Application.class,args);}}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 10:14:34

55.物流场景实战-限界上下文+CQRS设计物流追踪系统-附完整架构设计

55 物流场景实战:限界上下文 + CQRS 设计物流追踪系统 你好,欢迎来到第 55 讲。 这是我们“架构升级”阶段的毕业大作。在经历了限界上下文、上下文映射、CQRS、事件驱动等一系列“战略级”武器的洗礼后,是时候将它们全部投入战场,去攻克一个真正复杂的、系统级的业务难题…

作者头像 李华
网站建设 2026/4/9 17:51:15

56.架构阶段复盘-微服务拆分常见问题与解决方案-附问题诊断清单

56 架构阶段复盘:微服务拆分常见问题与解决方案 你好,欢迎来到第 56 讲。 这是我们“架构升级”阶段的最后一讲,也是一次对“战略设计”思想的全面复盘和升华。 在过去的十几讲中,我们学习了 DDD 的全套战略设计工具箱: 用限界上下文,作为微服务拆分的“第一性原理”。…

作者头像 李华
网站建设 2026/4/15 5:49:19

2025年毕业生最爱的6款降AI神器推荐(含免费查AI率工具)

在论文、报告、内容创作越来越严格的时代&#xff0c;查AI率、检测AI率、降AI率 已经成为学生、写作者、博主的日常需求。很多同学因为 AI率过高被导师指出“AI痕迹太重”&#xff0c;甚至退回重写。本文今天一次性告诉你&#xff1a; 检测AI率应该注意什么 免费查AI率的网站有…

作者头像 李华
网站建设 2026/4/12 10:46:32

结合Dinov2和YOLO介绍深度学习三层(输入、隐藏、输出)

本篇博文我将用通俗的方式解释深度学习中的各层&#xff0c;并说明DINOv2和YOLO分别属于哪一层&#xff1a; &#x1f3d7;️ 深度学习三层的通俗比喻 输入层 就像邮局的信件分拣员负责接收原始数据&#xff08;图片、文本、声音等&#xff09;将数据整理成计算机能理解的格式&…

作者头像 李华
网站建设 2026/4/13 23:37:35

排序算法汇总以及java实现

排序算法&#xff08;sorting algorithm&#xff09;用于对一组数据按照特定顺序进行排列。排序算法有着广泛的应用&#xff0c;因为有序数据通常能够被更高效地查找、分析和处理。运行快、原地、稳定、自适应、通用性好。显然&#xff0c;迄今为止尚未发现兼具以上所有特性的排…

作者头像 李华
网站建设 2026/4/10 8:11:03

AI搜索排名GEO优化零售行业案例分享

AI搜索排名GEO优化零售行业案例分享随着AI技术的不断发展&#xff0c;越来越多的零售企业开始利用AI工具来优化其在线搜索排名。其中&#xff0c;GEO&#xff08;Geographic Optimization&#xff09;优化作为一种有效的策略&#xff0c;已经在多个零售企业中取得了显著的效果。…

作者头像 李华