Spring SpEL 表达式全解析:从基础语法到实战场景
Spring 表达式语言(Spring Expression Language,简称 SpEL)是 Spring 框架提供的一种强大的表达式语言,支持在运行时解析和计算表达式,功能覆盖 “变量引用、方法调用、属性访问、逻辑运算” 等场景。它不仅可用于 XML / 注解配置中的占位符解析(如 ${user.name}
),还能在代码中动态处理复杂表达式(如解析注解中的动态逻辑)。从 “核心概念→基础语法→实战场景→高级特性” 四个维度,系统讲解 SpEL 的使用方法与最佳实践。
SpEL 核心价值与应用场景
SpEL 的核心是 “在运行时动态解析表达式”,解决传统硬编码无法应对的动态逻辑问题,典型应用场景包括:
应用场景 |
示例 |
核心价值 |
配置文件占位符解析 |
XML 中 ${db.url} 、注解中 @Value("${user.name}") |
实现配置与代码分离,支持外部化配置 |
注解动态逻辑 |
自定义注解中 @Log(template = "操作 ${#userId}") |
注解参数支持动态变量,提升注解灵活性 |
动态数据访问 |
解析对象属性 user.name 、调用方法 user.getAge() |
无需硬编码 getter/setter,动态操作对象 |
复杂逻辑计算 |
表达式 #price * (1 - #discount) 计算折扣价 |
支持算术 / 逻辑运算,简化动态计算代码 |
集合操作 |
过滤集合 #users.?[age > 18] 、获取首元素 #users[^1] |
简化集合筛选、排序、投影等操作 |
SpEL 基础:核心组件与执行流程
在使用 SpEL 前,需先理解其核心组件与执行流程,这是后续实战的基础。
核心组件
SpEL 的执行依赖三个核心组件:
组件 |
作用 |
核心类 / 接口 |
表达式解析器 |
将字符串表达式解析为 Expression 对象 |
ExpressionParser (常用实现 SpelExpressionParser ) |
解析上下文 |
定义表达式的语法规则(如模板分隔符) |
ParserContext (常用实现 TemplateParserContext ) |
计算上下文 |
提供表达式执行所需的变量、函数、类型等 |
EvaluationContext (常用实现 StandardEvaluationContext ) |
表达式对象 |
已解析的表达式,可执行计算 |
Expression (通过 parser.parseExpression() 获取) |
执行流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public String parseTemplateExpression(String template, Map<String, String> variables) { ExpressionParser parser = new SpelExpressionParser(); ParserContext parserContext = new TemplateParserContext("${", "}"); EvaluationContext context = new StandardEvaluationContext(); for (Map.Entry<String, String> entry : variables.entrySet()) { context.setVariable(entry.getKey(), entry.getValue()); } Expression expression = parser.parseExpression(template, parserContext); return expression.getValue(context, String.class); // 返回解析结果 }
|
流程拆解:
- 解析器初始化:
SpelExpressionParser
是 SpEL 的默认解析器,支持所有 SpEL 语法;
- 模板上下文配置:
TemplateParserContext("${", "}")
定义 “模板表达式” 的格式(区别于普通表达式),你的场景中 {#mediaName}
会被识别为模板变量;
- 变量注入:通过
EvaluationContext.setVariable()
注入动态变量,表达式中需用 #变量名
引用(如 #mediaName
);
- 表达式执行:
expression.getValue()
会先解析模板(替换变量),再返回计算结果(你的场景中是字符串替换,复杂场景会执行运算)。
SpEL 基础语法:从简单到复杂
SpEL 语法灵活且功能强大,掌握基础语法是应对复杂场景的前提。以下按 “变量引用→属性访问→方法调用→逻辑运算→集合操作” 分类讲解。
1. 变量引用(你的场景核心)
2. 属性访问(对象属性与静态属性)
支持访问对象的实例属性、静态属性,无需硬编码 getter 方法。
(1)实例属性访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class User { private String name; private int age; public User(String name, int age) { this.name = name; this.age = age; } }
User user = new User("李四", 25); EvaluationContext context = new StandardEvaluationContext(user);
ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name").getValue(context, String.class); // 结果:李四 int age = parser.parseExpression("age").getValue(context, int.class); // 结果:25
context.setVariable("user", user); String name2 = parser.parseExpression("#user.name").getValue(context, String.class); // 结果:李四
|
(2)静态属性与方法调用
通过 T(全类名)
引用类,进而访问静态属性或调用静态方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ExpressionParser parser = new SpelExpressionParser();
double pi = parser.parseExpression("T(java.lang.Math).PI").getValue(double.class); System.out.println(pi);
int max = parser.parseExpression("T(java.lang.Math).max(10, 20)").getValue(int.class); System.out.println(max);
class StringUtils { public static String reverse(String str) { return new StringBuilder(str).reverse().toString(); } } String reversed = parser.parseExpression("T(com.example.StringUtils).reverse('hello')").getValue(String.class); System.out.println(reversed);
|
3. 方法调用(实例方法与链式调用)
支持调用对象的实例方法,甚至链式调用(方法返回值继续调用方法):
1 2 3 4 5 6 7 8 9 10 11 12 13
| User user = new User("张三", 20); EvaluationContext context = new StandardEvaluationContext(); context.setVariable("user", user);
ExpressionParser parser = new SpelExpressionParser();
int nameLength = parser.parseExpression("#user.name.length()").getValue(context, int.class); System.out.println(nameLength);
String subStr = parser.parseExpression("#user.name.toUpperCase().substring(1)").getValue(context, String.class); System.out.println(subStr);
|
4. 算术与逻辑运算
支持常见的算术运算(+、-、*、/、%)、比较运算(>、<、==)、逻辑运算(&&、||、!):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| EvaluationContext context = new StandardEvaluationContext(); context.setVariable("price", 100.0); context.setVariable("discount", 0.8); context.setVariable("minPrice", 50.0);
ExpressionParser parser = new SpelExpressionParser();
double finalPrice = parser.parseExpression("#price * (1 - #discount)").getValue(context, double.class); System.out.println(finalPrice);
boolean isQualified = parser.parseExpression("#price * (1 - #discount) > #minPrice").getValue(context, boolean.class); System.out.println(isQualified);
|
5. 集合操作(筛选、排序、投影)
SpEL 对集合的支持极为强大,无需循环即可完成筛选、排序、投影等操作,语法简洁高效:
(1)集合筛选(?[]
)
筛选集合中满足条件的元素,返回新集合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| List<User> users = Arrays.asList( new User("张三", 18), new User("李四", 25), new User("王五", 30) );
EvaluationContext context = new StandardEvaluationContext(); context.setVariable("users", users);
ExpressionParser parser = new SpelExpressionParser();
List<User> adultUsers = parser.parseExpression("#users.?[age > 20]").getValue(context, new TypeReference<List<User>>() {}); System.out.println(adultUsers.size());
|
(2)集合投影(![]
)
提取集合中元素的某个属性,形成新的 “属性集合”(类似 Stream 的 map
操作):
1 2 3
| List<String> userNames = parser.parseExpression("#users.!['姓名:' + name]").getValue(context, new TypeReference<List<String>>() {}); System.out.println(userNames);
|
(3)集合首 / 尾元素(^[]
/$[]
)
快速获取集合的第一个或最后一个元素:
1 2 3
| User oldestUser = parser.parseExpression("#users.^[age > 0].sort(age).$[0]").getValue(context, User.class); System.out.println(oldestUser.getName());
|
实战场景深度解析
扩展两个高频实战场景,覆盖 “注解动态逻辑解析” 和 “配置文件复杂表达式”。
场景 1:解析注解中的动态模板(你的需求延伸)
假设你需要自定义一个 @Log
注解,其 content
属性支持 SpEL 模板表达式,用于动态生成日志内容(如包含用户 ID、操作类型):
步骤 1:定义自定义注解
1 2 3 4 5 6 7 8
| import java.lang.annotation.*;
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Log { String content(); }
|
步骤 2:AOP 切面解析注解表达式
通过 AOP 拦截标注 @Log
的方法,解析表达式并生成日志:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.expression.ExpressionParser; import org.springframework.expression.ParserContext; import org.springframework.expression.StandardEvaluationContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.TemplateParserContext; import org.springframework.stereotype.Component;
import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map;
@Aspect @Component public class LogAspect { private final ExpressionParser parser = new SpelExpressionParser(); private final ParserContext templateContext = new TemplateParserContext("${", "}");
@Pointcut("@annotation(com.example.Log)") public void logPointcut() {}
@AfterReturning("logPointcut()") public void afterReturning(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); Log logAnnotation = method.getAnnotation(Log.class); String logTemplate = logAnnotation.content();
Map<String, Object> variables = new HashMap<>(); variables.put("userId", 1001); Object[] args = joinPoint.getArgs(); if (args.length > 0) { variables.put("operation", args[0]); }
StandardEvaluationContext context = new StandardEvaluationContext(); variables.forEach(context::setVariable);
String logContent = parser.parseExpression(logTemplate, templateContext) .getValue(context, String.class);
System.out.println("[自动日志] " + logContent); } }
|
步骤 3:使用注解并测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import org.springframework.stereotype.Service;
@Service public class UserService { @Log(content = "用户 ${#userId} 执行 ${#operation} 操作成功") public void updateUser(String operation, Long userId) { System.out.println("更新用户 " + userId + " 的操作:" + operation); } }
public class LogTest { public static void main(String[] args) { UserService userService = new UserService(); userService.updateUser("update", 1001L); } }
|
场景 2:配置文件中的复杂表达式解析
除了代码中的动态解析,SpEL 还常用于 XML / 注解配置中的复杂表达式,例如在 @Value
中计算动态值:
(1)注解配置中使用 SpEL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component;
@Component public class AppConfig { @Value("#{systemProperties['user.home']}") private String userHome;
@Value("#{environment['PATH']}") private String systemPath;
@Value("#{100 * 0.8}") private double discountPrice;
@Value("#{userBean.name}") private String userName;
public String getUserHome() { return userHome; } public double getDiscountPrice() { return discountPrice; } }
|
(2)XML 配置中使用 SpEL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="orderBean" class="com.example.Order"> <property name="finalPrice" value="#{100 * (1 - 0.2)}"/> <property name="userName" value="#{userBean.name}"/> </bean>
<bean id="userBean" class="com.example.User"> <property name="name" value="张三"/> </bean> </beans>
|
SpEL 高级特性:类型转换与自定义函数
1. 自动类型转换
SpEL 支持自动将表达式结果转换为目标类型,无需手动强转,例如将字符串 true
转为布尔值、将数字字符串转为整数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| ExpressionParser parser = new SpelExpressionParser();
boolean isTrue = parser.parseExpression("'true'").getValue(boolean.class); System.out.println(isTrue);
int num = parser.parseExpression("'123'").getValue(int.class); System.out.println(num);
class StringToUserConverter implements Converter<String, User> { @Override public User convert(String source) { String[] parts = source.split(","); return new User(parts[0], Integer.parseInt(parts[1])); } }
StandardEvaluationContext context = new StandardEvaluationContext(); context.setTypeConverter(new DefaultConversionService() {{ addConverter(new StringToUserConverter()); }}); User user = parser.parseExpression("'李四,25'").getValue(context, User.class); System.out.println(user.getName());
|
2. 自定义函数
通过 EvaluationContext
注册自定义函数,让 SpEL 支持业务特定的复杂逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import org.springframework.expression.MethodExecutor; import org.springframework.expression.MethodResolver; import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.lang.reflect.Method;
public class CustomFunctionTest { public static double average(double a, double b) { return (a + b) / 2; }
public static void main(String[] args) { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("avg", CustomFunctionTest.class.getMethod("average", double.class, double.class));
double avg = parser.parseExpression("#avg(10, 20)").getValue(context, double.class); System.out.println(avg); } }
|
注意事项与避坑指南
- 线程安全:
ExpressionParser
(如 SpelExpressionParser
)是线程安全的,可全局复用;EvaluationContext
是非线程安全的,需为每个线程创建新实例(避免变量污染);
- 模板表达式 vs 普通表达式:
- 模板表达式:需通过
TemplateParserContext
定义分隔符(如 ${}
),适用于 “静态文本 + 动态变量” 场景(如你的日志模板);
- 普通表达式:无需分隔符,直接解析表达式(如
#user.name + #user.age
);
- 变量引用语法:
- 引用
EvaluationContext
中的变量:#变量名
;
- 引用根对象(
StandardEvaluationContext
构造时传入的对象)的属性:直接写属性名(无需 #
);
- 性能优化:频繁解析相同表达式时,可缓存
Expression
对象(解析过程耗时,执行过程高效);
- 安全风险:SpEL 支持反射访问私有属性和方法,若表达式来自外部输入(如用户提交的字符串),需过滤危险表达式(如
T(java.lang.Runtime).getRuntime().exec('rm -rf /')
),避免代码注入攻击。
总结
SpEL 是 Spring 框架中极具灵活性的特性,不仅能解决 “配置占位符解析”“注解动态逻辑” 等基础场景,还能应对 “复杂计算”“集合操作” 等高级需求。其核心优势在于:
- 动态性:运行时解析表达式,应对硬编码无法覆盖的动态逻辑;
- 简洁性:一行表达式替代多行文法计算、集合筛选代码;
- 集成性:无缝融入 Spring 配置与代码,支持变量、对象、函数的灵活组合
v1.3.10