PropertiesBeanDefinitionReader源码分析

master
xuchengsheng 2023-11-07 09:56:12 +08:00
parent 93857c8ce0
commit 4c1d4719d6
6 changed files with 321 additions and 0 deletions

View File

@ -17,6 +17,7 @@
<module>spring-bean-beanDefinitionHolder</module> <module>spring-bean-beanDefinitionHolder</module>
<module>spring-bean-beanDefinitionRegistry</module> <module>spring-bean-beanDefinitionRegistry</module>
<module>spring-bean-xmlBeanDefinitionReader</module> <module>spring-bean-xmlBeanDefinitionReader</module>
<module>spring-bean-propertiesBeanDefinitionReader</module>
</modules> </modules>
</project> </project>

View File

@ -0,0 +1,251 @@
## PropertiesBeanDefinitionReader
- [PropertiesBeanDefinitionReader](#propertiesbeandefinitionreader)
- [一、知识储备](#一知识储备)
- [二、基本描述](#二基本描述)
- [三、主要功能](#三主要功能)
- [四、最佳实践](#四最佳实践)
- [五、源码分析](#五源码分析)
- [六、与其他组件的关系](#六与其他组件的关系)
- [七、常见问题](#七常见问题)
### 一、知识储备
1. **Bean定义**
- 了解Bean的概念以及如何定义和配置Bean是非常重要的。这包括Bean的ID、类名、属性注入、依赖关系等。
2. **属性文件(.properties)**
+ 了解属性文件的基本格式和规则,将帮助我们正确编写属性文件,以便 `PropertiesBeanDefinitionReader` 能够正确地解析其中的配置信息并加载到 Spring 容器中。
### 二、基本描述
`PropertiesBeanDefinitionReader` 是 Spring 框架的一个组件,它的主要功能是从属性文件中加载 Bean 的配置信息,然后将这些配置信息解析并转化为 Spring 容器中的 Bean 定义。属性文件的格式通常是键值对,键表示 Bean 的名称,值表示 Bean 的类名或其他配置属性。这个过程允许我们将 Bean 配置与应用程序代码和 XML 配置文件分离,以实现动态加载和管理 Bean 定义,增强了应用程序的可扩展性和配置的灵活性。
### 三、主要功能
1. **加载属性文件**
+ `PropertiesBeanDefinitionReader` 通过 `loadBeanDefinitions(Resource resource)` 方法加载属性文件,其中的 `Resource` 可以是文件路径、类路径或其他资源标识符,指定了包含 Bean 定义信息的属性文件的位置。
2. **解析属性文件**
+ 一旦属性文件被加载,`PropertiesBeanDefinitionReader` 会解析文件中的内容,识别 Bean 的名称、类名以及其他配置属性。
3. **创建 Bean 定义**
+ 根据属性文件中的配置信息,`PropertiesBeanDefinitionReader` 创建相应的 `BeanDefinition` 对象,其中包含了 Bean 的定义信息,包括名称、类、属性、依赖关系等。
4. **注册 Bean 定义**
+ `PropertiesBeanDefinitionReader` 将解析得到的 Bean 定义注册到 Spring IOC 容器中,使这些 Bean 可以在应用程序中被实例化和使用。
### 四、最佳实践
我们创建了一个 Spring Bean 工厂(`DefaultListableBeanFactory`)和一个属性文件读取器(`PropertiesBeanDefinitionReader`),然后从属性文件中加载 Bean 的定义,然后通过 Bean 工厂获取相应的 Bean 对象,以实现动态加载和管理 Bean 配置
```java
public class PropertiesBeanDefinitionReaderDemo {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
PropertiesBeanDefinitionReader reader = new PropertiesBeanDefinitionReader(beanFactory);
// 从properties文件加载bean定义
reader.loadBeanDefinitions(new ClassPathResource("bean-definitions.properties"));
// 获取bean
System.out.println("myBean = " + beanFactory.getBean("myBean"));
System.out.println("myBean = " + beanFactory.getBean("myBean"));
}
}
```
属性文件 "`bean-definitions.properties`" 中的内容如下:
```properties
myBean.(class)=com.xcs.spring.bean.MyBean
myBean.message=hello world
myBean.(lazy-init)=true
myBean.(scope)=prototype
```
`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 + '\'' +
", hashCode='0x" + Integer.toHexString(System.identityHashCode(this)).toUpperCase() + '\'' +
'}';
}
}
```
运行结果发现,我们从属性文件中动态加载 Bean 配置,同时控制懒加载和作用域,以实现更灵活的 Bean 管理。在这个场景中,每次请求 "`myBeanA`" 都会得到一个新的实例,这是因为作用域设置为 "`prototype`"。
```java
myBean = MyBean{message='hello world', hashCode='0x6646153'}
myBean = MyBean{message='hello world', hashCode='0x45DD4EDA'}
```
### 五、源码分析
在`org.springframework.beans.factory.support.PropertiesBeanDefinitionReader#loadBeanDefinitions(resource)`方法中,又调用 `loadBeanDefinitions(encodedResource,prefix)` 方法来实际加载 Bean 定义。
```java
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource), null);
}
```
在`org.springframework.beans.factory.support.PropertiesBeanDefinitionReader#loadBeanDefinitions(encodedResource,prefix)`方法中,主要是从属性文件中加载和管理 Bean 配置,实现了 Spring 中属性文件的解析和注册。
```java
public int loadBeanDefinitions(EncodedResource encodedResource, @Nullable String prefix)
throws BeanDefinitionStoreException {
// 如果启用了跟踪级别的日志,则输出加载属性文件的日志信息
if (logger.isTraceEnabled()) {
logger.trace("Loading properties bean definitions from " + encodedResource);
}
// 创建 Properties 对象,用于存储属性文件中的键值对配置
Properties props = new Properties();
try {
try (InputStream is = encodedResource.getResource().getInputStream()) {
// 如果指定了编码,使用指定编码读取属性文件
if (encodedResource.getEncoding() != null) {
getPropertiesPersister().load(props, new InputStreamReader(is, encodedResource.getEncoding()));
}
// 否则,使用默认编码读取属性文件
else {
getPropertiesPersister().load(props, is);
}
}
// 注册 Bean 定义,将属性文件中的配置转化为 Bean 定义并注册到容器中
int count = registerBeanDefinitions(props, prefix, encodedResource.getResource().getDescription());
// 如果启用了调试级别的日志,则输出已加载的 Bean 定义数量的日志信息
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + encodedResource);
}
return count;
}
catch (IOException ex) {
// 处理异常,如果在读取或注册过程中发生异常,抛出 BeanDefinitionStoreException 异常
throw new BeanDefinitionStoreException("Could not parse properties from " + encodedResource.getResource(), ex);
}
}
```
在`org.springframework.beans.factory.support.PropertiesBeanDefinitionReader#registerBeanDefinitions(map, prefix,resourceDescription)`方法中,首先遍历属性文件中的键值对配置,根据配置的前缀以及键的格式,将合法的 Bean 定义注册到容器中。如果键不符合要求或已经注册,则会忽略。最终,该方法返回已注册的 Bean 定义数量,用于跟踪注册的 Bean 数量。
```java
public int registerBeanDefinitions(Map<?, ?> map, @Nullable String prefix, String resourceDescription)
throws BeansException {
if (prefix == null) {
prefix = "";
}
int beanCount = 0;
// 遍历属性文件中的键值对配置
for (Object key : map.keySet()) {
if (!(key instanceof String)) {
throw new IllegalArgumentException("Illegal key [" + key + "]: only Strings allowed");
}
String keyString = (String) key;
if (keyString.startsWith(prefix)) {
// 键的格式为prefix<名称>.属性
String nameAndProperty = keyString.substring(prefix.length());
// 查找属性名之前的点号,忽略属性键中的点号。
int sepIdx ;
int propKeyIdx = nameAndProperty.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX);
if (propKeyIdx != -1) {
sepIdx = nameAndProperty.lastIndexOf(SEPARATOR, propKeyIdx);
}
else {
sepIdx = nameAndProperty.lastIndexOf(SEPARATOR);
}
if (sepIdx != -1) {
String beanName = nameAndProperty.substring(0, sepIdx);
if (logger.isTraceEnabled()) {
logger.trace("Found bean name '" + beanName + "'");
}
if (!getRegistry().containsBeanDefinition(beanName)) {
// 如果还未注册该 Bean...
// 注册 Bean 定义
registerBeanDefinition(beanName, map, prefix + beanName, resourceDescription);
++beanCount;
}
}
else {
// 忽略该键:它不是有效的 Bean 名称和属性,
// 尽管它以所需的前缀开始。
if (logger.isDebugEnabled()) {
logger.debug("Invalid bean name and property [" + nameAndProperty + "]");
}
}
}
}
// 返回已注册的 Bean 定义数量
return beanCount;
}
```
在`org.springframework.beans.factory.support.PropertiesBeanDefinitionReader#registerBeanDefinition(beanName,map,prefix, resourceDescription)`方法中,主要是处理属性文件中的配置并将其转化为 Spring Bean 定义,然后注册到容器中。
```java
protected void registerBeanDefinition(String beanName, Map<?, ?> map, String prefix, String resourceDescription)
throws BeansException {
// ... [代码部分省略以简化]
try {
AbstractBeanDefinition bd = BeanDefinitionReaderUtils.createBeanDefinition(
parent, className, getBeanClassLoader());
bd.setScope(scope);
bd.setAbstract(isAbstract);
bd.setLazyInit(lazyInit);
bd.setConstructorArgumentValues(cas);
bd.setPropertyValues(pvs);
getRegistry().registerBeanDefinition(beanName, bd);
}
catch (ClassNotFoundException ex) {
throw new CannotLoadBeanClassException(resourceDescription, beanName, className, ex);
}
catch (LinkageError err) {
throw new CannotLoadBeanClassException(resourceDescription, beanName, className, err);
}
}
```
### 六、与其他组件的关系
1. **独立应用程序**
+ 在一些独立的 Spring 应用程序中,我们可以使用 `PropertiesBeanDefinitionReader` 来动态加载 Bean 定义,从而使应用程序的配置更加灵活。这样,应用程序可以根据需要动态配置和管理 Bean。
2. **基于属性文件的配置**
+ 一些应用程序采用基于属性文件的配置方式,将各种 Bean 的配置信息存储在属性文件中。`PropertiesBeanDefinitionReader` 可以用来加载这些配置文件,并将配置信息转化为 Spring 的 Bean 定义。
### 七、常见问题
1. **如何使用 `PropertiesBeanDefinitionReader`**
+ 使用 `PropertiesBeanDefinitionReader` 需要创建一个实例并与 Spring 的 Bean 工厂(如 `DefaultListableBeanFactory`)结合使用。然后,使用 `loadBeanDefinitions` 方法加载属性文件中的配置,并将 Bean 定义注册到容器中。
2. **属性文件的格式是什么?**
+ 属性文件的格式通常采用键值对key=value的形式其中键表示 Bean 的名称,而值表示 Bean 的配置信息。属性文件中可以包含 Bean 的类、作用域、属性值等信息。
3. **如何配置懒加载和作用域?**
+ 在属性文件中,我们可以使用 `lazy-init``scope` 来配置懒加载和作用域。例如,`myBean.(lazy-init)=true` 可以配置一个 Bean 为懒加载,而 `myBean.(scope)=prototype` 可以配置 Bean 的作用域为原型。
4. **什么是属性文件中的 Bean 名称?**
+ 属性文件中的 Bean 名称通常与配置的键key一致。例如`myBeanA=(class)=com.example.MyBean` 中的 Bean 名称是 "myBeanA"。
5. **为什么使用 `PropertiesBeanDefinitionReader`**
+ `PropertiesBeanDefinitionReader` 可以使应用程序更加灵活,允许我们将配置信息从代码中分离出来,从而实现应用程序的可配置性。它还支持动态加载和管理 Bean适用于需要动态配置的场景。

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-beans</artifactId>
<groupId>com.xcs.spring</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-bean-propertiesBeanDefinitionReader</artifactId>
</project>

View File

@ -0,0 +1,24 @@
package com.xcs.spring;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.PropertiesBeanDefinitionReader;
import org.springframework.core.io.ClassPathResource;
/**
* @author xcs
* @date 20231106 1009
**/
public class PropertiesBeanDefinitionReaderDemo {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
PropertiesBeanDefinitionReader reader = new PropertiesBeanDefinitionReader(beanFactory);
// 从properties文件加载bean定义
reader.loadBeanDefinitions(new ClassPathResource("bean-definitions.properties"));
// 获取bean
System.out.println("myBean = " + beanFactory.getBean("myBean"));
System.out.println("myBean = " + beanFactory.getBean("myBean"));
}
}

View File

@ -0,0 +1,26 @@
package com.xcs.spring.bean;
/**
* @author xcs
* @date 20231106 1011
**/
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 + '\'' +
", hashCode='0x" + Integer.toHexString(System.identityHashCode(this)).toUpperCase() + '\'' +
'}';
}
}

View File

@ -0,0 +1,4 @@
myBean.(class)=com.xcs.spring.bean.MyBean
myBean.message=hello world
myBean.(lazy-init)=true
myBean.(scope)=prototype