Java - Spring 表达式语言 (SpEL) 简单入门

发布于:2024-10-17 ⋅ 阅读:(8) ⋅ 点赞:(0)

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
  • 独立引用可以配置
<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属性。
更多的使用场景还需要大家的探索发现。