2023-11-04 09:04:05 +00:00
|
|
|
|
## XmlBeanDefinitionReader
|
|
|
|
|
|
|
|
|
|
- [XmlBeanDefinitionReader](#xmlbeandefinitionreader)
|
|
|
|
|
- [一、知识储备](#一知识储备)
|
|
|
|
|
- [二、基本描述](#二基本描述)
|
|
|
|
|
- [三、主要功能](#三主要功能)
|
2023-11-09 03:57:01 +00:00
|
|
|
|
- [四、最佳实践](#四最佳实践)
|
|
|
|
|
- [五、时序图](#五时序图)
|
2023-11-04 09:04:05 +00:00
|
|
|
|
- [六、源码分析](#六源码分析)
|
|
|
|
|
- [七、与其他组件的关系](#七与其他组件的关系)
|
|
|
|
|
- [八、常见问题](#八常见问题)
|
|
|
|
|
|
|
|
|
|
### 一、知识储备
|
|
|
|
|
|
2023-11-09 03:57:01 +00:00
|
|
|
|
1. **`Resource`**
|
|
|
|
|
- `Resource` 代表一个资源,可以是文件、类路径上的文件、URL 等。它提供了对资源的抽象和访问方法。
|
|
|
|
|
- [点击查看Resource接口](https://github.com/xuchengsheng/spring-reading/tree/master/spring-resources/spring-resource)
|
|
|
|
|
2. **`ResourceLoader`**
|
|
|
|
|
- `ResourceLoader` 可以用于获取资源,这些资源可以是文件、类路径上的资源、URL、甚至是远程资源。它提供了一种统一的方式来加载资源,无论这些资源位于何处。
|
|
|
|
|
- [点击查看ResourceLoader接口](https://github.com/xuchengsheng/spring-reading/tree/master/spring-resources/spring-resource-resourceLoader)
|
|
|
|
|
3. **`DocumentLoader`**
|
|
|
|
|
+ `XmlBeanDefinitionReader`依赖于`DocumentLoader`来加载和解析XML配置文件,以便可以将XML文件中的Bean定义信息转化为Spring容器内部的Bean定义对象。
|
|
|
|
|
+ [点击查看DocumentLoader接口](https://github.com/xuchengsheng/spring-reading/tree/master/spring-resources/spring-resource-documentLoader)
|
2023-11-04 09:04:05 +00:00
|
|
|
|
|
|
|
|
|
### 二、基本描述
|
|
|
|
|
|
|
|
|
|
`XmlBeanDefinitionReader`是Spring Framework中的一个类,用于加载和解析XML格式的Bean定义配置文件,将配置文件中定义的Bean元数据信息提取为Spring容器内部的Bean定义对象,进而实现IOC容器的构建和管理。这类负责读取XML配置文件,解析Bean的定义信息(包括ID、类名、属性、依赖等),并将这些定义注册到Spring应用程序上下文,使我们能够方便地配置和管理应用程序中的各种Bean组件。
|
|
|
|
|
|
|
|
|
|
### 三、主要功能
|
|
|
|
|
|
|
|
|
|
1. **加载XML配置文件**
|
|
|
|
|
+ `XmlBeanDefinitionReader`能够加载XML格式的Spring配置文件,通常使用Resource对象来指定配置文件的位置,如`ClassPathResource`、`FileSystemResource`等。
|
|
|
|
|
2. **解析XML文件**
|
|
|
|
|
+ 它将XML配置文件解析为Spring容器内部数据结构,使用`DocumentLoader`来实现。这包括将XML元素和属性转化为Spring的Bean定义信息。
|
|
|
|
|
3. **注册Bean定义**
|
|
|
|
|
+ 一旦`XmlBeanDefinitionReader`成功解析XML文件中的Bean定义信息,它会将这些信息注册到Spring容器的Bean工厂,以便容器能够创建和管理这些Bean实例。
|
|
|
|
|
|
2023-11-09 03:57:01 +00:00
|
|
|
|
### 四、最佳实践
|
2023-11-04 09:04:05 +00:00
|
|
|
|
|
|
|
|
|
首先创建了一个Spring容器(`DefaultListableBeanFactory`),然后使用`XmlBeanDefinitionReader`来加载和解析名为"`beans.xml`"的XML配置文件,将其中定义的Bean元数据信息注册到容器中。随后,通过容器获取名为"`myBean`"的Bean实例,最后将该Bean实例打印出来。这样的操作实现了Spring容器的初始化、XML配置文件的解析,以及Bean的获取和使用。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class XmlBeanDefinitionReaderDemo {
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
// 创建一个DefaultListableBeanFactory作为Spring容器
|
|
|
|
|
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
|
|
|
|
|
|
|
|
|
|
// 创建XmlBeanDefinitionReader实例用于解析XML配置
|
|
|
|
|
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
|
|
|
|
|
// 加载XML配置文件
|
|
|
|
|
reader.loadBeanDefinitions(new ClassPathResource("beans.xml"));
|
|
|
|
|
|
|
|
|
|
// 获取并使用Bean
|
|
|
|
|
MyBean myBean = factory.getBean("myBean", MyBean.class);
|
|
|
|
|
System.out.println("myBean = " + myBean);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`ClassPathResource("sample.xml")` 将加载类路径下名为 "`sample.xml`" 的资源文件的内容。在我们的示例配置文件中,这个资源文件定义了一个名为 "`myBean`" 的 Spring Bean,该 Bean 具有一个属性 "message",其值设置为 "Hello World"。
|
|
|
|
|
|
|
|
|
|
```xml
|
|
|
|
|
<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="myBean" class="com.xcs.spring.bean.MyBean">
|
|
|
|
|
<property name="message" value="Hello World"/>
|
|
|
|
|
</bean>
|
|
|
|
|
|
|
|
|
|
</beans>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`MyBean` 的Java类,代表了一个简单的Java Bean。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public class MyBean {
|
|
|
|
|
|
|
|
|
|
private String message;
|
|
|
|
|
|
|
|
|
|
public String getMessage() {
|
|
|
|
|
return message;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setMessage(String message) {
|
|
|
|
|
this.message = message;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String toString() {
|
|
|
|
|
return "MyBean{" +
|
|
|
|
|
"message='" + message + '\'' +
|
|
|
|
|
'}';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2023-11-09 03:57:01 +00:00
|
|
|
|
### 五、时序图
|
|
|
|
|
|
|
|
|
|
~~~mermaid
|
|
|
|
|
sequenceDiagram
|
|
|
|
|
autonumber
|
|
|
|
|
Title: XmlBeanDefinitionReader时序图
|
|
|
|
|
|
|
|
|
|
XmlBeanDefinitionReaderDemo->>XmlBeanDefinitionReader:loadBeanDefinitions(resource)
|
|
|
|
|
XmlBeanDefinitionReader->>XmlBeanDefinitionReader:loadBeanDefinitions(encodedResource)
|
|
|
|
|
note over XmlBeanDefinitionReader: 解码资源并加载Bean定义
|
|
|
|
|
|
|
|
|
|
XmlBeanDefinitionReader->>XmlBeanDefinitionReader:doLoadBeanDefinitions(inputSource,resource)
|
|
|
|
|
note over XmlBeanDefinitionReader: 使用InputSource加载Bean定义
|
|
|
|
|
|
|
|
|
|
XmlBeanDefinitionReader->>XmlBeanDefinitionReader:doLoadDocument(inputSource,resource)
|
|
|
|
|
note over XmlBeanDefinitionReader: 调用doLoadDocument方法解析XML
|
|
|
|
|
|
|
|
|
|
XmlBeanDefinitionReader->>DefaultDocumentLoader:loadDocument(...)
|
2023-11-09 04:05:16 +00:00
|
|
|
|
note over XmlBeanDefinitionReader,DefaultDocumentLoader: 加载XML
|
2023-11-09 03:57:01 +00:00
|
|
|
|
|
|
|
|
|
DefaultDocumentLoader->>XmlBeanDefinitionReader:返回Document
|
|
|
|
|
|
|
|
|
|
XmlBeanDefinitionReader->>XmlBeanDefinitionReader:registerBeanDefinitions(doc,resource)
|
|
|
|
|
note over XmlBeanDefinitionReader: 调用registerBeanDefinitions方法注册Bean定义
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
XmlBeanDefinitionReader->>DefaultBeanDefinitionDocumentReader:createBeanDefinitionDocumentReader()
|
|
|
|
|
note over XmlBeanDefinitionReader,DefaultBeanDefinitionDocumentReader: 创建BeanDefinitionDocumentReader
|
|
|
|
|
DefaultBeanDefinitionDocumentReader->>XmlBeanDefinitionReader:返回documentReader
|
|
|
|
|
|
|
|
|
|
XmlBeanDefinitionReader->>XmlReaderContext:new XmlReaderContext()
|
|
|
|
|
note over XmlBeanDefinitionReader,XmlReaderContext: 创建XmlReaderContext
|
|
|
|
|
XmlReaderContext->>XmlBeanDefinitionReader:返回readerContext
|
|
|
|
|
|
|
|
|
|
XmlBeanDefinitionReader->>DefaultBeanDefinitionDocumentReader:registerBeanDefinitions(doc,readerContext)
|
|
|
|
|
note over XmlBeanDefinitionReader,DefaultBeanDefinitionDocumentReader: 调用registerBeanDefinitions注册Bean定义
|
|
|
|
|
|
|
|
|
|
loop Every minute
|
|
|
|
|
DefaultBeanDefinitionDocumentReader->>DefaultBeanDefinitionDocumentReader:doRegisterBeanDefinitions(root)
|
2023-11-09 04:05:16 +00:00
|
|
|
|
note over DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader: 处理beans标签
|
|
|
|
|
|
2023-11-09 03:57:01 +00:00
|
|
|
|
DefaultBeanDefinitionDocumentReader->>DefaultBeanDefinitionDocumentReader:parseBeanDefinitions(root,delegate)
|
2023-11-09 04:05:16 +00:00
|
|
|
|
note over DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader: 处理“import”、“alias”、“bean”标签
|
|
|
|
|
|
2023-11-09 03:57:01 +00:00
|
|
|
|
DefaultBeanDefinitionDocumentReader->>DefaultBeanDefinitionDocumentReader:parseDefaultElement(ele,delegate)
|
2023-11-09 04:05:16 +00:00
|
|
|
|
note over DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader: 解析默认元素
|
2023-11-09 03:57:01 +00:00
|
|
|
|
|
|
|
|
|
alt 如果是import标签
|
|
|
|
|
DefaultBeanDefinitionDocumentReader->>DefaultBeanDefinitionDocumentReader:importBeanDefinitionResource(ele)
|
|
|
|
|
note over DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader: 处理import标签
|
|
|
|
|
|
|
|
|
|
else 如果是alias标签
|
|
|
|
|
DefaultBeanDefinitionDocumentReader->>DefaultBeanDefinitionDocumentReader:processAliasRegistration(ele)
|
|
|
|
|
note over DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader: 处理alias标签
|
|
|
|
|
|
|
|
|
|
else 如果是bean标签
|
2023-11-09 04:05:16 +00:00
|
|
|
|
rect rgb(207,207,207)
|
|
|
|
|
DefaultBeanDefinitionDocumentReader->>DefaultBeanDefinitionDocumentReader:processBeanDefinition(ele, delegate)
|
|
|
|
|
note over DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader: 处理bean标签
|
|
|
|
|
DefaultBeanDefinitionDocumentReader->>XmlBeanDefinitionReader:getRegistry()
|
|
|
|
|
note over DefaultBeanDefinitionDocumentReader,XmlBeanDefinitionReader: 获取BeanDefinitionRegistry
|
|
|
|
|
XmlBeanDefinitionReader->>DefaultBeanDefinitionDocumentReader:返回BeanDefinitionRegistry
|
|
|
|
|
DefaultBeanDefinitionDocumentReader->>BeanDefinitionReaderUtils:registerBeanDefinition(definitionHolder,registry)
|
|
|
|
|
note over DefaultBeanDefinitionDocumentReader,BeanDefinitionReaderUtils: 注册Bean定义
|
|
|
|
|
BeanDefinitionReaderUtils->>BeanDefinitionReaderUtils:registerBeanDefinition(beanName,beanDefinition)
|
|
|
|
|
note over BeanDefinitionReaderUtils: 注册Bean定义到容器
|
|
|
|
|
end
|
2023-11-09 03:57:01 +00:00
|
|
|
|
else 如果是beans标签
|
|
|
|
|
DefaultBeanDefinitionDocumentReader->>DefaultBeanDefinitionDocumentReader:doRegisterBeanDefinitions(ele)
|
|
|
|
|
note over DefaultBeanDefinitionDocumentReader,DefaultBeanDefinitionDocumentReader: 处理beans标签(重新递归)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
~~~
|
|
|
|
|
|
2023-11-04 09:04:05 +00:00
|
|
|
|
### 六、源码分析
|
|
|
|
|
|
|
|
|
|
在`org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(resource)`方法中,又调用了 `loadBeanDefinitions(encodedResource)` 方法,同时将 `resource` 包装成一个 `EncodedResource` 对象。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Override
|
|
|
|
|
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
|
|
|
|
|
return loadBeanDefinitions(new EncodedResource(resource));
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在`org.springframework.beans.factory.xml.XmlBeanDefinitionReader#loadBeanDefinitions(encodedResource)`方法中,首先是创建一个 `InputSource` 对象,用于解析XML。最后调用 `doLoadBeanDefinitions` 方法,实际的XML解析和Bean定义加载。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
|
|
|
|
|
// ... [代码部分省略以简化]
|
|
|
|
|
|
|
|
|
|
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
|
|
|
|
|
// 创建一个 InputSource 对象,用于解析XML
|
|
|
|
|
InputSource inputSource = new InputSource(inputStream);
|
|
|
|
|
|
|
|
|
|
// 如果 encodedResource 包含字符编码信息,设置 InputSource 的编码
|
|
|
|
|
if (encodedResource.getEncoding() != null) {
|
|
|
|
|
inputSource.setEncoding(encodedResource.getEncoding());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 调用 doLoadBeanDefinitions 方法,实际的XML解析和Bean定义加载
|
|
|
|
|
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
|
|
|
|
|
}
|
|
|
|
|
catch (IOException ex) {
|
|
|
|
|
// ... [代码部分省略以简化]
|
|
|
|
|
}
|
|
|
|
|
finally {
|
|
|
|
|
// ... [代码部分省略以简化]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在`org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadBeanDefinitions`方法中,主要用于加载XML配置文件,解析其中的Bean定义,并注册这些Bean定义到Spring容器中
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
|
|
|
|
|
throws BeanDefinitionStoreException {
|
|
|
|
|
try {
|
|
|
|
|
// 解析XML文档
|
|
|
|
|
Document doc = doLoadDocument(inputSource, resource);
|
|
|
|
|
// 注册Bean定义并获取已加载的Bean数量
|
|
|
|
|
int count = registerBeanDefinitions(doc, resource);
|
|
|
|
|
if (logger.isDebugEnabled()) {
|
|
|
|
|
logger.debug("Loaded " + count + " bean definitions from " + resource);
|
|
|
|
|
}
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ... [代码部分省略以简化]
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2023-11-09 03:57:01 +00:00
|
|
|
|
在`org.springframework.beans.factory.xml.XmlBeanDefinitionReader#doLoadDocument`方法中,主要由`DocumentLoader`执行实际的XML加载和解析操作,并将解析后的`Document`对象返回。
|
|
|
|
|
|
|
|
|
|
+ [点击查看DocumentLoader接口](https://github.com/xuchengsheng/spring-reading/tree/master/spring-resources/spring-resource-documentLoader)
|
2023-11-04 09:04:05 +00:00
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
|
|
|
|
|
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
|
|
|
|
|
getValidationModeForResource(resource), isNamespaceAware());
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在`org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions`方法中,首先创建一个 `BeanDefinitionDocumentReader` 实例,然后调用它的 `registerBeanDefinitions` 方法,将XML文档中的Bean定义注册到Spring容器中,最后返回成功注册的Bean数量。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
|
|
|
|
|
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
|
|
|
|
|
int countBefore = getRegistry().getBeanDefinitionCount();
|
|
|
|
|
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
|
|
|
|
|
return getRegistry().getBeanDefinitionCount() - countBefore;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在`org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#registerBeanDefinitions`方法中,又调用 `doRegisterBeanDefinitions` 方法,开始实际的Bean定义注册过程。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Override
|
|
|
|
|
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
|
|
|
|
|
this.readerContext = readerContext;
|
|
|
|
|
doRegisterBeanDefinitions(doc.getDocumentElement());
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在`org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions`方法中,核心内容是,XML的预处理、Bean定义的解析和XML的后处理。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
protected void doRegisterBeanDefinitions(Element root) {
|
|
|
|
|
// ... [代码部分省略以简化]
|
|
|
|
|
|
|
|
|
|
// 调用 preProcessXml 方法,执行XML预处理操作
|
|
|
|
|
preProcessXml(root);
|
|
|
|
|
|
|
|
|
|
// 调用 parseBeanDefinitions 方法,解析Bean定义
|
|
|
|
|
parseBeanDefinitions(root, this.delegate);
|
|
|
|
|
|
|
|
|
|
// 调用 postProcessXml 方法,执行XML后处理操作
|
|
|
|
|
postProcessXml(root);
|
|
|
|
|
|
|
|
|
|
// ... [代码部分省略以简化]
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在`org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions`方法中,主要解析XML文档中的Bean定义元素,根据元素所属的命名空间(默认命名空间或自定义命名空间)分别调用合适的方法进行解析。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
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)) {
|
|
|
|
|
// 如果子元素也属于默认命名空间,解析为默认元素
|
|
|
|
|
parseDefaultElement(ele, delegate);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// 否则解析为自定义元素
|
|
|
|
|
delegate.parseCustomElement(ele);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// 如果根元素不属于默认命名空间,解析为自定义元素
|
|
|
|
|
delegate.parseCustomElement(root);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在`org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseDefaultElement`方法中,主要负责根据元素的类型选择合适的方法进行处理,从而实现了XML配置文件中不同元素的解析和Bean定义的注册。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
|
|
|
|
|
// 根据元素的名称进行不同的处理
|
|
|
|
|
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
|
2023-11-09 07:13:54 +00:00
|
|
|
|
// 步骤1: 如果是 <import> 元素,导入其他Bean定义资源
|
2023-11-04 09:04:05 +00:00
|
|
|
|
importBeanDefinitionResource(ele);
|
|
|
|
|
}
|
|
|
|
|
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
|
2023-11-09 07:13:54 +00:00
|
|
|
|
// 步骤2: 如果是 <alias> 元素,处理Bean的别名注册
|
2023-11-04 09:04:05 +00:00
|
|
|
|
processAliasRegistration(ele);
|
|
|
|
|
}
|
|
|
|
|
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
|
2023-11-09 07:13:54 +00:00
|
|
|
|
// 步骤3: 如果是 <bean> 元素,处理普通Bean定义
|
2023-11-04 09:04:05 +00:00
|
|
|
|
processBeanDefinition(ele, delegate);
|
|
|
|
|
}
|
|
|
|
|
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
|
2023-11-09 07:13:54 +00:00
|
|
|
|
// 步骤4: 如果是 <beans> 元素,递归注册内部Bean定义
|
2023-11-04 09:04:05 +00:00
|
|
|
|
doRegisterBeanDefinitions(ele);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2023-11-09 07:13:54 +00:00
|
|
|
|
> `org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseDefaultElement`步骤1]
|
|
|
|
|
|
|
|
|
|
在`org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#importBeanDefinitionResource`方法中,主要是对XML文档中import元素指定的其他资源进行解析和加载,从而实现多个XML配置文件的合并与包含功能。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
protected void importBeanDefinitionResource(Element ele) {
|
|
|
|
|
// 获取import元素的resource属性值
|
|
|
|
|
String location = ele.getAttribute(RESOURCE_ATTRIBUTE);
|
|
|
|
|
|
|
|
|
|
// 检查resource属性值是否为空字符串
|
|
|
|
|
if (!StringUtils.hasText(location)) {
|
|
|
|
|
// 抛出错误信息
|
|
|
|
|
getReaderContext().error("Resource location must not be empty", ele);
|
|
|
|
|
// 返回空结果
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 解析resource属性值中的占位符变量
|
|
|
|
|
location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location);
|
|
|
|
|
|
|
|
|
|
// 创建一个新的LinkedHashSet,用于存储所有解析后的实际资源
|
|
|
|
|
Set<Resource> actualResources = new LinkedHashSet<>(4);
|
|
|
|
|
|
|
|
|
|
// 判断resource属性值是否为绝对路径
|
|
|
|
|
boolean absoluteLocation = false;
|
|
|
|
|
try {
|
|
|
|
|
// 如果resource属性值符合URL格式,或者可以被转换为一个绝对URI,那么认为它是一个绝对路径
|
|
|
|
|
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute();
|
|
|
|
|
} catch (URISyntaxException ex) {
|
|
|
|
|
// ... [代码部分省略以简化]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果resource属性值是一个绝对路径
|
|
|
|
|
if (absoluteLocation) {
|
|
|
|
|
try {
|
|
|
|
|
// 使用父类的loadBeanDefinitions方法加载资源,并将结果添加到actualResources集合中
|
|
|
|
|
int importCount = getReaderContext().getReader().loadBeanDefinitions(location, actualResources);
|
|
|
|
|
|
|
|
|
|
// ... [代码部分省略以简化]
|
|
|
|
|
} catch (BeanDefinitionStoreException ex) {
|
|
|
|
|
// ... [代码部分省略以简化]
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 如果resource属性值不是一个绝对路径
|
|
|
|
|
try {
|
|
|
|
|
int importCount;
|
|
|
|
|
// 尝试在当前文件所在的目录下找到resource属性值所指向的资源
|
|
|
|
|
Resource relativeResource = getReaderContext().getResource().createRelative(location);
|
|
|
|
|
if (relativeResource.exists()) {
|
|
|
|
|
// 加载找到的资源,并将结果添加到actualResources集合中
|
|
|
|
|
importCount = getReaderContext().getReader().loadBeanDefinitions(relativeResource);
|
|
|
|
|
actualResources.add(relativeResource);
|
|
|
|
|
} else {
|
|
|
|
|
// 如果在当前文件所在的目录下找不到resource属性值所指向的资源,那么认为resource属性值是一个相对于当前文件所在位置的相对路径
|
|
|
|
|
String baseLocation = getReaderContext().getResource().getURL().toString();
|
|
|
|
|
// 调整resource属性值,使其成为一个绝对路径
|
|
|
|
|
String adjustedLocation = StringUtils.applyRelativePath(baseLocation, location);
|
|
|
|
|
|
|
|
|
|
// 加载调整后的资源,并将结果添加到actualResources集合中
|
|
|
|
|
importCount = getReaderContext().getReader().loadBeanDefinitions(adjustedLocation, actualResources);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ... [代码部分省略以简化]
|
|
|
|
|
} catch (IOException | BeanDefinitionStoreException ex) {
|
|
|
|
|
// ... [代码部分省略以简化]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将actualResources集合中的所有资源转换为数组,并调用fireImportProcessed方法通知监听器已经完成了一次import操作
|
|
|
|
|
Resource[] actResArray = actualResources.toArray(new Resource[0]);
|
|
|
|
|
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele));
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> `org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseDefaultElement`步骤2]
|
|
|
|
|
|
|
|
|
|
在`org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#processAliasRegistration`方法中,主要处理XML配置文件中的alias元素的方法。alias元素用于创建别名,即为已有的Bean ID分配一个新名称。这样,我们就可以在Bean引用时使用新的别名而不是原始的Bean ID。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
protected void processAliasRegistration(Element ele) {
|
|
|
|
|
// 获取alias元素的name属性值和alias属性值
|
|
|
|
|
String name = ele.getAttribute(NAME_ATTRIBUTE);
|
|
|
|
|
String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
|
|
|
|
|
|
|
|
|
|
// 判断两个属性值是否都有效
|
|
|
|
|
boolean valid = true;
|
|
|
|
|
if (!StringUtils.hasText(name)) {
|
|
|
|
|
// 抛出错误信息
|
|
|
|
|
getReaderContext().error("Name must not be empty", ele);
|
|
|
|
|
// 设置标志位,表明该别名注册无效
|
|
|
|
|
valid = false;
|
|
|
|
|
}
|
|
|
|
|
if (!StringUtils.hasText(alias)) {
|
|
|
|
|
// 抛出错误信息
|
|
|
|
|
getReaderContext().error("Alias must not be empty", ele);
|
|
|
|
|
// 设置标志位,表明该别名注册无效
|
|
|
|
|
valid = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果两个属性值都有效
|
|
|
|
|
if (valid) {
|
|
|
|
|
try {
|
|
|
|
|
// 调用Registry接口的registerAlias方法为原始Bean ID注册一个别名
|
|
|
|
|
getReaderContext().getRegistry().registerAlias(name, alias);
|
|
|
|
|
} catch (Exception ex) {
|
|
|
|
|
// ... [代码部分省略以简化]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 调用fireAliasRegistered方法通知监听器已经完成了一次别名注册操作
|
|
|
|
|
getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
> [`org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseDefaultElement`步骤3]
|
|
|
|
|
|
2023-11-04 09:04:05 +00:00
|
|
|
|
在`org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#processBeanDefinition`方法中,主要是解析 `<bean>` 元素,注册Bean定义到Spring容器。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
|
|
|
|
|
// 调用 BeanDefinitionParserDelegate 的 parseBeanDefinitionElement 方法,解析 <bean> 元素
|
|
|
|
|
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
|
|
|
|
|
|
|
|
|
|
if (bdHolder != null) {
|
|
|
|
|
// 如果解析成功,调用 delegate.decorateBeanDefinitionIfRequired 方法,如果需要的话装饰 Bean 定义
|
|
|
|
|
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 注册最终装饰后的 Bean 定义
|
|
|
|
|
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
|
|
|
|
|
} catch (BeanDefinitionStoreException ex) {
|
|
|
|
|
// 处理注册时的异常,如果注册失败,抛出 BeanDefinitionStoreException 异常
|
|
|
|
|
getReaderContext().error("Failed to register bean definition with name '" +
|
|
|
|
|
bdHolder.getBeanName() + "'", ele, ex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 发送 Bean 注册事件
|
|
|
|
|
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在`org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition`方法中,将Bean定义注册到Spring容器的Bean定义注册表中,并处理别名的注册。
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public static void registerBeanDefinition(
|
|
|
|
|
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
|
|
|
|
|
throws BeanDefinitionStoreException {
|
|
|
|
|
|
|
|
|
|
// Register bean definition under primary name.
|
|
|
|
|
String beanName = definitionHolder.getBeanName();
|
|
|
|
|
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
|
|
|
|
|
|
|
|
|
|
// Register aliases for bean name, if any.
|
|
|
|
|
String[] aliases = definitionHolder.getAliases();
|
|
|
|
|
if (aliases != null) {
|
|
|
|
|
for (String alias : aliases) {
|
|
|
|
|
registry.registerAlias(beanName, alias);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 七、与其他组件的关系
|
|
|
|
|
|
|
|
|
|
1. **`ApplicationContext`的实现类**
|
|
|
|
|
+ Spring的`ApplicationContext`接口有多个实现,其中一些实现类在其内部使用`XmlBeanDefinitionReader`来处理XML配置文件。例如,`ClassPathXmlApplicationContext`、`FileSystemXmlApplicationContext`和`XmlWebApplicationContext`都使用`XmlBeanDefinitionReader`。
|
|
|
|
|
2. **Custom XML配置加载**
|
|
|
|
|
+ 如果我们编写自己的Spring应用程序或自定义的Spring容器,我们可以使用`XmlBeanDefinitionReader`来实现自定义的XML配置加载逻辑。
|
|
|
|
|
3. **单独的`XmlBeanDefinitionReader`使用**
|
|
|
|
|
+ 有时,我们可能需要在应用程序中手动创建`XmlBeanDefinitionReader`实例,然后使用它来加载和注册Bean定义。
|
|
|
|
|
|
|
|
|
|
### 八、常见问题
|
|
|
|
|
|
|
|
|
|
1. **XML文件路径正确性**
|
|
|
|
|
+ 确保XML配置文件的路径和名称正确,相对路径或绝对路径都要检查,否则将导致文件无法找到或读取。
|
|
|
|
|
2. **XML文件格式和编码**
|
|
|
|
|
+ XML文件必须符合XML语法规则,包括标签嵌套、属性格式和XML声明。同时,确保文件使用正确的字符编码,与指定的编码一致,否则可能导致解析错误或乱码。
|
|
|
|
|
3. **Bean定义的正确性**
|
|
|
|
|
+ XML文件中的Bean定义必须正确,包括Bean的属性、名称、类路径等。不正确的Bean定义可能导致Spring容器初始化失败。
|
|
|
|
|
4. **版本兼容性**
|
|
|
|
|
+ 确保所使用的Spring框架版本与XML配置文件中的XML schema(XSD)版本兼容。不同版本可能需要不同的XSD版本。
|
|
|
|
|
5. **错误处理和日志记录**
|
|
|
|
|
+ 在出现问题时,查看Spring框架的错误日志和异常堆栈信息,以便更好地识别和解决配置问题。及时的错误处理和日志记录有助于定位问题并加快排查过程。
|