深入解析 Spring SpEL:SpelExpressionParser 的使用与实践

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

前一段时间看到JetCache框架的源码中使用了Spring SpEL,回想到之前看到某框架的源码时,也看到类似的技术。于是自己也试着学习一下。

我们一直在借助框架使用着SpEL技术,但是可能却不能单独拎出来使用过。Spring中有很多实用的工具类,我们可以直接使用。

今天我们一起看看SpEL的表达式的用法。

02 基本概念

Spring 表达式语言(SpEL)是一种强大的表达式解析框架,集成于 Spring 生态中,用于在运行时动态计算表达式值。SpelExpressionParserSpEL 的核心解析器,能够将字符串表达式转换为可执行的 Expression 对象,支持对象属性访问、方法调用、运算符操作、集合处理等复杂逻辑。

典型应用场景

  • Spring 注解(如 @Value
  • Spring Security 权限表达式
  • XML/注解配置的动态参数
  • 规则引擎、动态公式计算
  • 模板渲染(如 Thymeleaf)

03 案例

3.1 基础使用案例

解析简单的表达式。

java 复制代码
@Test
void test01() {
    ExpressionParser parser = new SpelExpressionParser();
    Expression exp = parser.parseExpression("'Hello World'.concat('!')");
    String message = (String) exp.getValue();
    System.out.println(message);
    // Hello World!
    
     Expression intExp = parser.parseExpression("25 * 4");
     Integer message = (Integer) intExp.getValue();
     System.out.println(message);
    // 100
   
    
    Expression boolExp = parser.parseExpression("100 > 50 && true");
    System.out.println(boolExp.getValue()); 
    // 输出: true
}

简单来说,就是将一个字符串解析成对应的运算或者方法调用。

3.2 对象属性访问与方法调用

通过上下文访问对象或者调用对象的方法

java 复制代码
@Test
void test02() {
    User user = new User("Alice", 30);
    ExpressionParser parser = new SpelExpressionParser();
    
    // 创建上下文并绑定对象
    EvaluationContext context = new StandardEvaluationContext(user);

    // 访问属性
    Expression nameExp = parser.parseExpression("name");
    // 从上下文中获取对象属性
    System.out.println(nameExp.getValue(context)); 
    // 输出: Alice

    // 调用方法
    Expression methodExp = parser.parseExpression("setName('Bob')");
    methodExp.getValue(context); // 执行setName方法
    System.out.println(user.getName()); 
    // 输出: Bob

    // 链式操作
    Expression chainExp = parser.parseExpression("name.toUpperCase().length()");
    System.out.println(chainExp.getValue(context)); 
    // 输出: 3 (BOB的长度) 
}

绑定上下文有两种方式:

  • 构造函数:new StandardEvaluationContext(user)
  • Setter方法:new StandardEvaluationContext().setVariable("user", user);

两种方式的取值各不相同:

  • 构造函数取值:直接获取属性parser.parseExpression("name");
  • Setter方法取值:通过#号调用parser.parseExpression("#user.name");

3.3 集合的操作

SpEL 支持对集合进行过滤、投影等操作:

java 复制代码
@Test
void test03() {
    List<User> users = Arrays.asList(
        new User("Alice", 25),
        new User("Bob", 30),
        new User("Charlie", 20)
    );
    
    EvaluationContext context = new StandardEvaluationContext();
    context.setVariable("users", users);

    ExpressionParser parser = new SpelExpressionParser();

    // 过滤年龄>25的用户
    Expression filterExp = parser.parseExpression("#users.?[age > 25]");
    List<User> filtered = (List<User>) filterExp.getValue(context);
    filtered.forEach(u -> System.out.println(u.getName())); 
    // 输出: Bob

    // 提取所有用户名
    Expression mapExp = parser.parseExpression("#users.![name.toUpperCase()]");
    List<String> names = (List<String>) mapExp.getValue(context);
    System.out.println(names); 
    // 输出: [ALICE, BOB, CHARLIE]
}

值得说明的是:提取集合的某个属性时,用到的!,过滤或者取值用到?

04 使用注意事项

4.1 安全风险

3.2案例中,可以看到:我们是可以随意修改上下文变量的值的。如果担心被人恶意篡改上下文参数,我们需要使用org.springframework.expression.spel.support.SimpleEvaluationContext的限制功能(默认禁用类型构造、方法调用):

java 复制代码
SimpleEvaluationContext simpleContext = SimpleEvaluationContext.forReadOnlyDataBinding().build();
exp.getValue(simpleContext); // 安全模式

再次修改参数就是出现下面的报错:

4.2 空值的处理

NPE是每一个程序员都会遇到的异常,我们看看Spring SpEL如何处理:使用 ?. 安全导航操作符避免 NPE

java 复制代码
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("book", null);

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("#book?.bookName");

// 返回 null,不会出现NPE

4.3 类型转化

明确指定返回值类型:

java 复制代码
String name = exp.getValue(context, String.class);

05 小结

SpelExpressionParser 是 Spring 生态中处理动态表达式的利器,通过简洁的语法实现了:

  • 对象属性/方法动态访问
  • 复杂集合操作
  • 与 Spring 配置深度集成

掌握 SpEL 能显著提升开发灵活性,尤其在需要动态配置的场景中(如规则引擎、个性化配置),但务必警惕安全风险,做好防护措施。

相关推荐
二闹4 分钟前
三个注解,到底该用哪一个?别再傻傻分不清了!
后端
用户490558160812515 分钟前
当控制面更新一条 ACL 规则时,如何更新给数据面
后端
林太白17 分钟前
Nuxt.js搭建一个官网如何简单
前端·javascript·后端
码事漫谈19 分钟前
VS Code 终端完全指南
后端
该用户已不存在44 分钟前
OpenJDK、Temurin、GraalVM...到底该装哪个?
java·后端
怀刃1 小时前
内存监控对应解决方案
后端
TT哇1 小时前
@[TOC](计算机是如何⼯作的) JavaEE==网站开发
java·redis·java-ee
大模型教程1 小时前
Cursor 快速入门指南:从安装到核心功能
程序员·llm·cursor
码事漫谈1 小时前
VS Code Copilot 内联聊天与提示词技巧指南
后端
Tina学编程1 小时前
48Days-Day19 | ISBN号,kotori和迷宫,矩阵最长递增路径
java·算法