spring-reading./spring-spel/spring-spel-propertyAccessor/README.md

260 lines
14 KiB
Markdown
Raw Normal View History

## PropertyAccessor
- [PropertyAccessor](#propertyaccessor)
- [一、基本信息](#一基本信息)
- [二、知识储备](#二知识储备)
- [三、基本描述](#三基本描述)
- [四、主要功能](#四主要功能)
- [五、接口源码](#五接口源码)
- [六、主要实现](#六主要实现)
- [七、最佳实践](#七最佳实践)
- [八、与其他组件的关系](#八与其他组件的关系)
- [九、常见问题](#九常见问题)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading)
### 二、知识储备
1. **Spring 表达式语言SpEL**
+ 了解 SpEL 的基本语法和功能,包括表达式的解析、运算符、变量、函数等。
2. **对象属性访问与反射机制**
+ 了解如何使用 Java 的反射机制来访问和操作对象的属性,包括获取类的字段、调用对象的方法、设置字段的值等。
3. **设计模式**
+ 理解常见的设计模式,如适配器模式、策略模式等,这些设计模式在实现自定义 `PropertyAccessor` 接口时可能会用到。
### 三、基本描述
`PropertyAccessor` 接口的作用在于定义了一套统一的属性访问规范,通过该接口可以实现对对象属性的读取和写入操作,使得 Spring 框架中的各个模块能够统一地访问对象的属性,从而实现更加灵活和可扩展的应用开发。通过实现这个接口,可以定制化对象属性的访问逻辑,以满足不同场景下的需求,例如在 Spring 表达式语言SpEL、数据绑定、AOP 编程等方面发挥作用。
### 四、主要功能
1. **属性读取**
+ 提供了 `canRead()` 方法用于判断是否可以读取指定属性,以及 `read()` 方法用于读取属性的值。这使得可以通过统一的方式从对象中获取属性值。
2. **属性写入**
+ 提供了 `canWrite()` 方法用于判断是否可以写入指定属性,以及 `write()` 方法用于设置属性的值。这使得可以通过统一的方式向对象中设置属性值。
3. **自定义属性访问逻辑**
+ 允许我们根据实际需求实现自定义的属性访问逻辑,例如通过自定义的 `PropertyAccessor` 实现类来访问对象的属性,从而实现特定的业务逻辑或行为。
4. **适配不同的对象结构**
+ 可以针对不同的对象结构实现不同的 `PropertyAccessor` 实现类,以适应不同类型的对象,包括 JavaBean、Map、数组等。
### 五、接口源码
`PropertyAccessor` 的接口,它用于读取和写入对象的属性。接口中包含了四个方法,分别用于判断是否能够读取属性、读取属性值、判断是否能够写入属性以及写入属性值。此外,接口中还包含了一个用于获取特定目标类数组的方法。该接口提供了一种统一的方式来访问对象的属性,使得可以实现自定义的属性访问逻辑,并能够适配不同的对象结构。
```java
/**
* 属性访问器能够读取(可能写入)对象的属性。
*
* <p>这个接口没有限制因此实现者可以自由地直接访问属性作为字段或通过getter方法或其他方式访问属性。
*
* <p>解析器可以选择指定一个目标类的数组,用于调用它。然而,如果从{@link #getSpecificTargetClasses()}返回{@code null},它将被调用
* 以尝试解析所有属性引用,并有机会确定它是否可以读取或写入它们。
*
* <p>属性解析器被认为是有序的,并且每个都将依次被调用。影响调用顺序的唯一规则是,在{@link #getSpecificTargetClasses()}中直接命名目标类的任何解析器
* 将在通用解析器之前首先被调用。
*
* @author Andy Clement
* @since 3.0
*/
public interface PropertyAccessor {
/**
* 返回此解析器应该被调用的类数组。
* <p>返回{@code null}表示这是一个通用解析器,可以在任何类型上尝试解析属性。
* @return 适用于此解析器的类数组(或{@code null}如果是通用解析器)
*/
@Nullable
Class<?>[] getSpecificTargetClasses();
/**
* 用于确定解析器实例是否能够访问指定目标对象上的指定属性。
* @param context 尝试进行访问的评估上下文
* @param target 要访问属性的目标对象
* @param name 要访问的属性的名称
* @return 如果此解析器能够读取属性则为true
* @throws AccessException 如果有任何问题确定是否可以读取属性
*/
boolean canRead(EvaluationContext context, @Nullable Object target, String name) throws AccessException;
/**
* 用于从指定的目标对象中读取属性。
* 只有当{@link #canRead}也返回{@code true}时才能成功。
* @param context 尝试进行访问的评估上下文
* @param target 要访问属性的目标对象
* @param name 要访问的属性的名称
* @return 包装读取的属性值和其类型描述符的TypedValue对象
* @throws AccessException 如果有任何问题访问属性值
*/
TypedValue read(EvaluationContext context, @Nullable Object target, String name) throws AccessException;
/**
* 用于确定解析器实例是否能够向指定目标对象上的指定属性写入。
* @param context 尝试进行访问的评估上下文
* @param target 要访问属性的目标对象
* @param name 要访问的属性的名称
* @return 如果此解析器能够写入属性则为true
* @throws AccessException 如果有任何问题确定是否可以写入属性
*/
boolean canWrite(EvaluationContext context, @Nullable Object target, String name) throws AccessException;
/**
* 用于向指定目标对象的属性写入。
* 只有当{@link #canWrite}也返回{@code true}时才能成功。
* @param context 尝试进行访问的评估上下文
* @param target 要访问属性的目标对象
* @param name 要访问的属性的名称
* @param newValue 属性的新值
* @throws AccessException 如果有任何问题写入属性值
*/
void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue)
throws AccessException;
}
```
### 六、主要实现
1. **BeanExpressionContextAccessor**
+ 允许在表达式中访问特定的对象和属性。通过 `BeanExpressionContextAccessor`,可以在 SpEL 表达式中直接访问上下文中的对象属性,这在某些场景下非常有用,例如在 Spring 的 `@Value` 注解中使用 SpEL 表达式读取配置属性。
2. **BeanFactoryAccessor**
+ 允许在 SpEL 表达式中访问 Spring `BeanFactory` 中注册的 Bean 对象的属性。`BeanFactory` 是 Spring IoC 容器的核心接口,用于管理和创建 Bean。通过 `BeanFactoryAccessor`,可以在 SpEL 表达式中访问 IoC 容器中注册的 Bean读取和设置其属性值这为在 SpEL 中访问 Spring Bean 提供了便利。
3. **CompilablePropertyAccessor**
+ 允许对属性访问进行优化以提高性能。通过编译能力,可以在运行时对属性访问逻辑进行优化,从而减少不必要的计算和提高执行效率,这在某些性能要求较高的场景下非常有用。
4. **DataBindingPropertyAccessor**
+ 允许在 Spring 应用程序中读取和写入属性。数据绑定是将用户输入绑定到后端数据模型的过程,在 Web 应用程序中广泛应用。`DataBindingPropertyAccessor` 提供了方便的方式来读取和设置对象的属性值,用于处理数据绑定过程中的属性访问需求。
5. **EnvironmentAccessor**
+ `EnvironmentAccessor` 用于从 Spring 环境中访问属性例如读取配置文件中的属性值。Spring 的环境抽象提供了一种统一的方式来访问应用程序的环境属性,包括系统属性、环境变量、配置文件等。通过 `EnvironmentAccessor`,可以在 SpEL 表达式中直接访问 Spring 环境中的属性值,从而实现动态的配置和属性注入。
6. **JspPropertyAccessor**
+ 允许在 JSP 页面中使用 SpEL 表达式,并在 JSP 标签中访问对象的属性。JSP 是一种常见的 Web 视图技术,与 SpEL 结合使用可以实现更加灵活和动态的页面渲染。通过在 JSP 中使用 SpEL 表达式,并在自定义标签(如 `eval` 标签)内访问对象的属性,可以实现更加灵活和可配置的页面逻辑。
7. **MapAccessor**
+ `MapAccessor` 用于在 SpEL 表达式中直接访问 Map 对象中的键值对。Map 是一种常见的数据结构,用于存储键值对的集合。通过 `MapAccessor`,可以在 SpEL 表达式中直接访问 Map 对象中的键值对,读取和设置其属性值,这为在 SpEL 中操作 Map 提供了便利。
8. **OptimalPropertyAccessor**
+ `OptimalPropertyAccessor` 提供了属性访问的优化实现,通常在 `ReflectivePropertyAccessor` 内部使用,用于提高属性访问的性能。通过对属性访问逻辑的优化,可以减少不必要的计算和提高执行效率,从而更加高效地访问对象的属性值。
### 七、最佳实践
使用 `ReflectivePropertyAccessor` 对象来访问对象的属性。首先,创建了一个 `MyBean` 对象,并设置了其属性值。然后,创建了一个 `StandardEvaluationContext` 对象作为属性访问的上下文。接着,通过 `canRead()` 方法判断是否可以读取对象的属性,如果可以则使用 `read()` 方法读取属性值,并通过 `canWrite()` 方法判断是否可以写入属性,如果可以则使用 `write()` 方法更新属性值。最后,输出了更新后的属性值。
```java
public class PropertyAccessorDemo {
public static void main(String[] args) throws AccessException {
// 创建 ReflectivePropertyAccessor 对象
ReflectivePropertyAccessor propertyAccessor = new ReflectivePropertyAccessor();
// 创建一个对象,我们将在表达式中访问它的属性
MyBean myBean = new MyBean();
myBean.setName("spring-reading");
// 创建一个 EvaluationContext 对象
StandardEvaluationContext context = new StandardEvaluationContext();
// 演示 read 方法
if (propertyAccessor.canRead(context, myBean, "name")) {
System.out.println("Name: " + propertyAccessor.read(context, myBean, "name"));
}
// 演示 write 方法
if (propertyAccessor.canWrite(context, myBean, "name")) {
propertyAccessor.write(context, myBean, "name", "spring-reading-xcs");
System.out.println("Updated Name: " + myBean.getName());
}
}
}
```
`MyBean` 类定义了一个简单的 Java Bean具有一个名为 `name` 的私有字段和相应的 getter 和 setter 方法。
```java
public class MyBean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
```
运行结果发现在输出结果中“Name” 表示读取到的属性值,而 “Updated Name” 则表示经过写入后的新属性值。
```properties
Name: TypedValue: 'spring-reading' of [java.lang.String]
Updated Name: spring-reading-xcs
```
### 八、与其他组件的关系
1. **Spring 表达式语言SpEL**
+ 在 SpEL 中,`PropertyAccessor` 接口被用于访问对象的属性。SpEL 提供了一种在运行时动态求值表达式的机制,通过 `PropertyAccessor` 接口可以实现对对象属性的读写操作。
2. **Spring 数据绑定**
+ 在 Spring 中,数据绑定是一种常见的操作,用于将用户提交的数据绑定到后端数据模型上。`PropertyAccessor` 接口提供了一种统一的方式来访问对象的属性,可以在数据绑定过程中使用。
3. **Spring AOP 编程**
+ 在 Spring 的面向切面编程AOP`PropertyAccessor` 接口可以用于访问被增强对象的属性。通过 AOP 可以实现对被增强对象的属性进行拦截、修改等操作。
4. **Spring IOC 容器**
+ 在 Spring 的控制反转IoC容器中`PropertyAccessor` 接口可以用于访问注册在容器中的 Bean 对象的属性。通过 IoC 容器,可以方便地管理和访问对象的属性。
5. **其他自定义组件**
+ 除了以上提到的场景之外,`PropertyAccessor` 接口还可以被自定义组件使用。例如,在自定义的框架或库中,可以使用 `PropertyAccessor` 接口来实现对对象属性的访问操作,以实现特定的功能或逻辑。
### 九、常见问题
1. **属性访问权限问题**
+ 在使用 `PropertyAccessor` 接口时,可能会遇到对象的属性访问权限不足的问题,导致无法读取或写入属性。这可能涉及到对象的访问控制和权限设置。
2. **属性命名问题**
+ 在访问对象的属性时,可能会出现属性命名不一致的问题,例如大小写不匹配、拼写错误等,导致无法正确访问属性。
3. **属性类型转换问题**
+ 在进行属性读取或写入时,可能会遇到属性类型转换失败的问题,例如尝试将一个字符串值赋给一个整数类型的属性,导致转换异常。
4. **上下文环境配置问题**
+ 在使用 `PropertyAccessor` 接口时,需要提供一个合适的上下文环境(如 `EvaluationContext`),可能会出现配置错误或环境设置不当的问题,导致属性访问失败。
5. **异常处理问题**
+ 在属性访问过程中,可能会出现各种异常情况,例如空指针异常、访问权限异常等,需要进行适当的异常处理以保证程序的稳定性和可靠性。