一、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为例:
java
// 1. 构造器调用
public ClassPathXmlApplicationContext(String configLocation) {
this(new String[] {configLocation}, true, null);
}
// 2. refresh()方法 - Spring容器启动的核心方法
public void refresh() throws BeansException {
synchronized (this.startupShutdownMonitor) {
// 准备刷新上下文
prepareRefresh();
// 【关键步骤】获取BeanFactory,在此过程中完成XML解析
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 后续步骤...
prepareBeanFactory(beanFactory);
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
// ...
}
}
// 3. obtainFreshBeanFactory() - 触发XML解析
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory(); // 在这里进行XML解析
return getBeanFactory();
}
// 4. refreshBeanFactory() - AbstractRefreshableApplicationContext
protected final void refreshBeanFactory() {
// 创建BeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
// 【核心】加载BeanDefinition
loadBeanDefinitions(beanFactory);
}
// 5. loadBeanDefinitions() - AbstractXmlApplicationContext
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) {
// 创建XML的BeanDefinition读取器
XmlBeanDefinitionReader beanDefinitionReader =
new XmlBeanDefinitionReader(beanFactory);
// 使用Reader读取XML并注册BeanDefinition
loadBeanDefinitions(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:
java
// DefaultDocumentLoader.java
public Document loadDocument(InputSource inputSource,
EntityResolver entityResolver,
ErrorHandler errorHandler,
int validationMode,
boolean namespaceAware) {
// 创建DocumentBuilderFactory
DocumentBuilderFactory factory = createDocumentBuilderFactory(
validationMode, namespaceAware);
// 创建DocumentBuilder
DocumentBuilder builder = createDocumentBuilder(
factory, entityResolver, errorHandler);
// 解析为Document对象(DOM树)
return builder.parse(inputSource);
}
2.1.3 解析过程
java
// 1. 加载Document
Document doc = documentLoader.loadDocument(...);
// 2. 获取根元素
Element root = doc.getDocumentElement();
// 3. 遍历解析(以bean标签为例)
NodeList nodeList = root.getElementsByTagName("bean");
for (int i = 0; i < nodeList.getLength(); i++) {
Element beanElement = (Element) nodeList.item(i);
// 4. 提取属性
String id = beanElement.getAttribute("id");
String className = beanElement.getAttribute("class");
String scope = beanElement.getAttribute("scope");
// 5. 创建BeanDefinition
BeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClassName(className);
bd.setScope(scope);
// 6. 解析子元素(property、constructor-arg等)
parsePropertyElements(beanElement, bd);
// 7. 注册到BeanFactory
registry.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采用事件驱动模型:
java
// SAX事件处理器
public class MyHandler extends DefaultHandler {
// 1. 文档开始事件
public void startDocument() {
System.out.println("开始解析文档");
}
// 2. 元素开始事件
public void startElement(String uri, String localName,
String qName, Attributes attributes) {
if ("bean".equals(qName)) {
String id = attributes.getValue("id");
String className = attributes.getValue("class");
// 处理bean定义...
}
}
// 3. 元素内容事件
public void characters(char[] ch, int start, int length) {
String content = new String(ch, start, length);
// 处理文本内容...
}
// 4. 元素结束事件
public void endElement(String uri, String localName, String qName) {
if ("bean".equals(qName)) {
// bean标签结束,注册BeanDefinition
}
}
// 5. 文档结束事件
public void endDocument() {
System.out.println("文档解析完成");
}
}
2.2.2 解析过程
java
// 1. 创建SAX解析器工厂
SAXParserFactory factory = SAXParserFactory.newInstance();
// 2. 创建解析器
SAXParser parser = factory.newSAXParser();
// 3. 创建事件处理器
MyHandler handler = new MyHandler();
// 4. 开始解析(触发事件)
parser.parse(new File("beans.xml"), handler);
2.2.3 事件触发顺序示例
对于以下XML:
xml
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="userService" class="com.example.UserService">
<property name="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之间:
java
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = factory.createXMLStreamReader(
new FileInputStream("beans.xml"));
while (reader.hasNext()) {
int event = reader.next(); // 主动拉取事件
if (event == XMLStreamConstants.START_ELEMENT) {
String tagName = reader.getLocalName();
if ("bean".equals(tagName)) {
String id = reader.getAttributeValue(null, "id");
String className = reader.getAttributeValue(null, "class");
// 处理...
}
}
}
特点对比:
| 特性 | SAX | StAX |
|---|---|---|
| 模型 | 推式(事件驱动) | 拉式(迭代器模式) |
| 控制 | 解析器控制流程 | 应用程序控制流程 |
| 易用性 | 需要回调处理 | 更直观,类似迭代器 |
| 性能 | 略快 | 略慢 |
2.3.2 JDOM 和 DOM4J
这些是第三方XML解析库,不是Spring默认使用的方式:
java
// DOM4J示例
SAXReader reader = new SAXReader();
Document document = reader.read(new File("beans.xml"));
Element root = document.getRootElement();
for (Element bean : root.elements("bean")) {
String id = bean.attributeValue("id");
String className = bean.attributeValue("class");
// 处理...
}
三、Spring为什么选择DOM解析
3.1 Spring的选择理由
| 理由 | 说明 |
|---|---|
| 配置文件通常较小 | Spring配置文件一般不超过几MB,DOM的内存开销可接受 |
| 需要随机访问 | 解析bean依赖关系需要随机访问不同节点 |
| 需要支持命名空间 | Spring支持自定义命名空间,DOM更容易处理 |
| 易于扩展 | DOM API更容易实现自定义标签解析 |
| 向后兼容 | 历史原因,保持API稳定性 |
3.2 Spring的优化措施
虽然使用DOM,但Spring做了很多优化:
java
// 1. 延迟加载BeanDefinition
// 只在第一次获取Bean时才解析
// 2. 缓存解析结果
// BeanDefinition注册后会缓存,避免重复解析
// 3. 支持@Lazy注解
// 延迟Bean的实例化
// 4. 支持条件注册
// @Conditional可以跳过不必要的Bean注册
四、DOM vs SAX 深度对比
4.1 技术对比表
| 对比维度 | DOM | SAX |
|---|---|---|
| 解析方式 | 树形结构,全部加载 | 事件驱动,边读边解析 |
| 内存占用 | 高(与文件大小成正比) | 低(固定大小) |
| 解析速度 | 慢(需要构建完整树) | 快(流式处理) |
| 访问方式 | 随机访问 | 顺序访问 |
| 遍历方向 | 双向 | 单向(前向) |
| 修改能力 | 支持 | 不支持 |
| API复杂度 | 简单直观 | 复杂(需要状态管理) |
| 适用场景 | 中小型文件、需要修改 | 大型文件、流式处理 |
| XPath支持 | 支持 | 不支持 |
| 线程安全 | 不安全(需要同步) | 每个解析器独立使用 |
4.2 性能对比
以10MB的XML文件为例:
DOM解析:
- 内存占用:约40-50MB(4-5倍文件大小)
- 解析时间:约2-3秒
- 优势:解析后访问快速
SAX解析:
- 内存占用:约5-10MB(固定开销)
- 解析时间:约0.5-1秒
- 优势:内存友好,适合大文件
4.3 代码复杂度对比
DOM解析代码(简洁):
java
Document doc = builder.parse(xmlFile);
NodeList beans = doc.getElementsByTagName("bean");
for (int i = 0; i < beans.getLength(); i++) {
Element bean = (Element) beans.item(i);
String id = bean.getAttribute("id");
// 直接访问,逻辑清晰
}
SAX解析代码(复杂):
java
public class BeanHandler extends DefaultHandler {
private Stack<String> elementStack = new Stack<>();
private BeanDefinition currentBean;
public void startElement(String uri, String localName,
String qName, Attributes attributes) {
elementStack.push(qName);
if ("bean".equals(qName)) {
currentBean = new BeanDefinition();
currentBean.setId(attributes.getValue("id"));
} else if ("property".equals(qName) && isInBean()) {
// 需要手动维护状态
}
}
private boolean isInBean() {
return elementStack.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 关键方法调用链
java
// 1. XmlBeanDefinitionReader.loadBeanDefinitions()
public int loadBeanDefinitions(Resource resource) {
return loadBeanDefinitions(new EncodedResource(resource));
}
// 2. doLoadBeanDefinitions()
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) {
// 加载Document(使用DOM)
Document doc = doLoadDocument(inputSource, resource);
// 注册BeanDefinition
return registerBeanDefinitions(doc, resource);
}
// 3. registerBeanDefinitions()
public int registerBeanDefinitions(Document doc, Resource resource) {
BeanDefinitionDocumentReader documentReader =
createBeanDefinitionDocumentReader();
// 委托给DocumentReader处理
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
}
// 4. DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions()
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
// 解析beans标签
parseBeanDefinitions(root, this.delegate);
}
// 5. parseBeanDefinitions() - 遍历子元素
protected void parseBeanDefinitions(Element root,
BeanDefinitionParserDelegate delegate) {
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)) {
// 默认命名空间(bean、import、alias等)
parseDefaultElement(ele, delegate);
} else {
// 自定义命名空间(aop、tx、context等)
delegate.parseCustomElement(ele);
}
}
}
}
六、实际应用建议
6.1 Spring配置文件优化
xml
<!-- ❌ 避免:单个超大配置文件 -->
<beans>
<!-- 上千个bean定义... -->
</beans>
<!-- ✅ 推荐:拆分为多个模块 -->
<beans>
<import resource="spring-dao.xml"/>
<import resource="spring-service.xml"/>
<import resource="spring-web.xml"/>
</beans>
6.2 何时考虑SAX
如果需要处理超大型XML(非Spring配置场景):
java
// 场景:处理1GB的数据导出XML
public class LargeXmlProcessor extends DefaultHandler {
private int recordCount = 0;
@Override
public void startElement(String uri, String localName,
String qName, Attributes attributes) {
if ("record".equals(qName)) {
// 处理单条记录
processRecord(attributes);
recordCount++;
if (recordCount % 10000 == 0) {
System.out.println("Processed " + recordCount + " records");
}
}
}
private void processRecord(Attributes attributes) {
// 处理并立即释放,不保留在内存
}
}
6.3 现代Spring项目建议
java
// 优先使用注解配置,避免XML
@Configuration
@ComponentScan("com.example")
public class AppConfig {
@Bean
public UserService userService() {
return new UserServiceImpl();
}
}
// 或使用Spring Boot
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}