Java - Spring 表达式语言 (SpEL) 简单入门
文章目录
引言
Spring 表达式语言(SpEL是Spring Expression Language的简称)是一种功能强大的表达式语言,支持在运行时查询和操作对象图。功能定义在spring-expression-6.1.3.jar包中。虽然 SpEL 是 Spring 产品组合中表达式评估的基础,但它不直接与 Spring 绑定,可以独立使用。
以下使用单元测试的形式,对SeEL的使用有个简单了解和入门学习。
一、环境
- Springboot 3.2.2
- jdk-17.0.9
- Maven 3.3.9
- windows 10
二、资料
三、引用SpEL依赖
- Springboot3中已经默认引用了SpEL包,引用结构是
- spring-boot-starter-web
- spring-webmvc
- spring-expression
- spring-webmvc
- spring-boot-starter-web
- 独立引用可以配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>6.1.3</version>
<scope>compile</scope>
</dependency>
四、SeEL支持的功能
- 表达式语言支持以下功能:
- 字面表达式
- 布尔和关系运算符
- 正则表达式
- 类表达式
- 访问属性、数组、列表和映射
- 方法调用
- 关系运算符
- 调用构造函数
- bean引用
- 数组构造
- 内联的list
- 内联的map
- 三元运算符
- 变量
- 用户自定义函数
- 集合选择
- 模板化表达式
基础1:获取对象值
通过SpEL获取user对象中的id属性。
/*
* #this 和 #root 变量的区别
* #this 变量始终被定义并引用当前评估对象(针对那些非限定引用被解析)。
* #root 变量始终被定义并引用根上下文对象。
* */
@Test
public void getValueTest() {
var user = new User(11,"zhangsan");
// 1 定义解析器
SpelExpressionParser parser = new SpelExpressionParser();
// 2 使用解析器解析表达式
Expression exp = parser.parseExpression("#this.id");
// 3 获取解析结果
Integer id = (Integer) exp.getValue(user);
System.out.println(id);
}
基础2:获取对象值
基于StandardEvaluationContext评估上下文对象,获取user对象值。
/**
* 测试SpEL表达式的求值功能
* 使用StandardEvaluationContext来设置变量的值,并通过SpelExpressionParser解析表达式,最后通过表达式获取变量的值。
*/
@Test
public void getValue4EvaluationTest() {
var user = new User(11,"zhangsan");
//
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("parm",user);
//定义解析器
SpelExpressionParser parser = new SpelExpressionParser();
//使用解析器解析表达式
Expression exp = parser.parseExpression("#parm.id");
//获取解析结果
Integer id = (Integer) exp.getValue(context);
System.out.println(id);
}
基础3:集合对象象的访问
使用索引[x]的形式访问集合中的user对象的id属性。
/**
* 测试SpEL表达式中#this和#root变量的区别
* 在SpEL表达式中,#this和#root是两个特殊的变量,它们在使用时有着明显的区别。
* #this变量始终被定义并引用当前评估对象(针对那些非限定引用被解析)。
* #root变量始终被定义并引用根上下文对象。
* 本测试用例演示了如何在SpEL表达式中使用#this变量来获取列表中指定索引的元素属性。
*/
@Test
public void getValue4ListTest() {
/*
* #this 和 #root 变量的区别
* #this 变量始终被定义并引用当前评估对象(针对那些非限定引用被解析)。
* #root 变量始终被定义并引用根上下文对象。
* */
var users = getUsers();
print();
// 1 定义解析器
SpelExpressionParser parser = new SpelExpressionParser();
// 2 使用解析器解析表达式
Expression exp = parser.parseExpression("#this[2].id");
// 3 获取解析结果
Integer id = (Integer) exp.getValue(users);
System.out.println(id);
}
基础4:使用SeEL对属性赋值
使用SimpleEvaluationContext评估,对user的id属性赋值。
/**
* 测试使用SpEL表达式设置对象属性值
* <p>本测试用例演示了如何使用SpEL表达式来设置对象的属性值。首先创建了一个User对象,并打印出来。
* 然后定义了SpEL表达式解析器,并构建了用于读写数据绑定的评估上下文。接着使用SpEL表达式"id"来设置User对象的id属性值为22。
* 最后,使用另一个SpEL表达式"#this.id"来获取User对象的id属性值,并打印出来。同时,再次打印User对象以验证属性值是否被成功设置。
* <p>注意事项:
* - #this 变量始终被定义并引用当前评估对象(针对那些非限定引用被解析)。
* - #root 变量始终被定义并引用根上下文对象,但在本测试用例中未使用。
*/
@Test
public void setValueTest() {
/*
* #this 和 #root 变量的区别
* #this 变量始终被定义并引用当前评估对象(针对那些非限定引用被解析)。
* #root 变量始终被定义并引用根上下文对象。
* */
var user = new User(11,"zhangsan");
System.out.println(user);
// 1 定义解析器
SpelExpressionParser parser = new SpelExpressionParser();
// 2 设置值
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
parser.parseExpression("id").setValue(context,user, 22);
// 3 使用解析器解析表达式
Expression exp = parser.parseExpression("#this.id");
// 4 获取解析结果
Integer id = (Integer) exp.getValue(user);
System.out.println(id);
System.out.println(user);
}
完整测试用例
import org.junit.jupiter.api.Test;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
public class SpELTest {
List<User> users = new ArrayList<>();
//@BeforeEach
public List<User> getUsers() {
System.out.println("~~~~~~~~~~~~~~~~ init");
for (int i = 0; i < 10; i++) {
User user = new User();
user.id = i;
user.name = "name" + i;
users.add(user);
}
return users;
}
//@AfterEach
public void print() {
System.out.println("~~~~~~~~~~~~~~~~ print");
users.forEach(user -> System.out.println(user));
}
/**
* 测试SpEL表达式解析功能
* 通过SpEL表达式获取对象的属性值
*/
@Test
public void getValueTest() {
var user = new User(11,"zhangsan");
// 1 定义解析器
SpelExpressionParser parser = new SpelExpressionParser();
// 2 使用解析器解析表达式
Expression exp = parser.parseExpression("#this.id");
// 3 获取解析结果
Integer id = (Integer) exp.getValue(user);
System.out.println(id);
}
/**
* 测试SpEL表达式的求值功能
* 使用StandardEvaluationContext来设置变量的值,并通过SpelExpressionParser解析表达式,最后通过表达式获取变量的值。
*/
@Test
public void getValue4EvaluationTest() {
var user = new User(11,"zhangsan");
//
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("parm",user);
//定义解析器
SpelExpressionParser parser = new SpelExpressionParser();
//使用解析器解析表达式
Expression exp = parser.parseExpression("#parm.id");
//获取解析结果
Integer id = (Integer) exp.getValue(context);
System.out.println(id);
}
/**
* 测试SpEL表达式中#this和#root变量的区别
* 在SpEL表达式中,#this和#root是两个特殊的变量,它们在使用时有着明显的区别。
* #this变量始终被定义并引用当前评估对象(针对那些非限定引用被解析)。
* #root变量始终被定义并引用根上下文对象。
* 本测试用例演示了如何在SpEL表达式中使用#this变量来获取列表中指定索引的元素属性。
*/
@Test
public void getValue4ListTest() {
/*
* #this 和 #root 变量的区别
* #this 变量始终被定义并引用当前评估对象(针对那些非限定引用被解析)。
* #root 变量始终被定义并引用根上下文对象。
* */
var users = getUsers();
print();
// 1 定义解析器
SpelExpressionParser parser = new SpelExpressionParser();
// 2 使用解析器解析表达式
Expression exp = parser.parseExpression("#this[2].id");
// 3 获取解析结果
Integer id = (Integer) exp.getValue(users);
System.out.println(id);
}
/**
* 测试使用SpEL表达式设置对象属性值
* <p>本测试用例演示了如何使用SpEL表达式来设置对象的属性值。首先创建了一个User对象,并打印出来。
* 然后定义了SpEL表达式解析器,并构建了用于读写数据绑定的评估上下文。接着使用SpEL表达式"id"来设置User对象的id属性值为22。
* 最后,使用另一个SpEL表达式"#this.id"来获取User对象的id属性值,并打印出来。同时,再次打印User对象以验证属性值是否被成功设置。
* <p>注意事项:
* - #this 变量始终被定义并引用当前评估对象(针对那些非限定引用被解析)。
* - #root 变量始终被定义并引用根上下文对象,但在本测试用例中未使用。
*/
@Test
public void setValueTest() {
/*
* #this 和 #root 变量的区别
* #this 变量始终被定义并引用当前评估对象(针对那些非限定引用被解析)。
* #root 变量始终被定义并引用根上下文对象。
* */
var user = new User(11,"zhangsan");
System.out.println(user);
// 1 定义解析器
SpelExpressionParser parser = new SpelExpressionParser();
// 2 设置值
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
parser.parseExpression("id").setValue(context,user, 22);
// 3 使用解析器解析表达式
Expression exp = parser.parseExpression("#this.id");
// 4 获取解析结果
Integer id = (Integer) exp.getValue(user);
System.out.println(id);
System.out.println(user);
}
private class User {
public User() {}
public User(int id) {
this.id = id;
}
public User(int id, String name){
this.id = id;
this.name = name;
}
public int id;
public String name;
public String age;
@Override
public String toString() {
return MessageFormat.format("id={0}\tname={1}\tage={2}\t@{3}"
,id
,name
,age
,Integer.toHexString(hashCode()));
}
}
}
总结
SpEL语法简单,容易上手,在面向切面编程中有很大的发挥空间。
例如:库表中只记录了用户id没有记录用户名,但界面需要展示用户(userName),类似接口很多如果对每个返回值都处理一遍耗时耗力。
此时可以基于AOP的思想,把需要做转换的方法做切面,监控其返回值。通过SpEL动态读取的user对象的id属性,基于id反查用户名后再回写到user对象的userName属性中(表中无userName字段,但实体中需要定义)。
当然用到SpEL是因为每个表对id的定义不同,可以使用“自定义注解+SpEL表达式”方式,把依赖用到的取值字段定义到user对象的userName属性上,如:@MyRel(“parm.id”),表示userName的反查需要用到当前对象的id属性。
更多的使用场景还需要大家的探索发现。