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

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属性。

更多的使用场景还需要大家的探索发现。

相关推荐
李慕婉学姐6 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆8 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin8 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model20058 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉9 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国9 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_941882489 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈9 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_999 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹10 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理