Java - Spring 表达式语言 (SpEL) 简单入门
文章目录
- Java - Spring 表达式语言 (SpEL) 简单入门
- 引言
- 一、环境
- 二、资料
- 三、引用SpEL依赖
- 四、SeEL支持的功能
- 基础1:获取对象值
- 基础2:获取对象值
- 基础3:集合对象象的访问
- 基础4:使用SeEL对属性赋值
- 完整测试用例
- 总结
引言
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
二、资料
- Spring Expression Language (SpEL)
三、引用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 变量始终被定义并引用根上下文对象,但在本测试用例中未使用。*/@Testpublic 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<>();//@BeforeEachpublic 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;}//@AfterEachpublic void print() {System.out.println("~~~~~~~~~~~~~~~~ print");users.forEach(user -> System.out.println(user));}/*** 测试SpEL表达式解析功能* 通过SpEL表达式获取对象的属性值*/@Testpublic 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解析表达式,最后通过表达式获取变量的值。*/@Testpublic 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变量来获取列表中指定索引的元素属性。*/@Testpublic 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 变量始终被定义并引用根上下文对象,但在本测试用例中未使用。*/@Testpublic 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;@Overridepublic 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属性。
更多的使用场景还需要大家的探索发现。