深入解析 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 能显著提升开发灵活性,尤其在需要动态配置的场景中(如规则引擎、个性化配置),但务必警惕安全风险,做好防护措施。

相关推荐
_風箏12 分钟前
Java【代码 15】文件操作相关方法(获取文件、复制文件、创建文件夹、获取图片文件、写出数据到文件、清理文件夹)
后端
积极向上的zzz21 分钟前
java中一些数据结构的转换
java·开发语言·数据结构
Bohemian24 分钟前
实现一个单机版令牌桶限流器(字节)
后端·面试
风一样的树懒27 分钟前
ES是如何实现Master选举的?
后端
00后程序员28 分钟前
移动端 WebView 调试实战,多平台行为差异排查与统一调试流程
后端
千睢28 分钟前
JAVA中的反射
java·开发语言
Hejjon1 小时前
携带参数的表单文件上传 axios, SpringBoot
java·spring boot·后端
sivdead1 小时前
从被动查询到主动智能:数据应用智能体的技术演进路线图
人工智能·后端·架构
典孝赢麻崩乐急1 小时前
Java学习-----JVM的垃圾回收算法
java·jvm·学习