SpEL 表达式
SpEL(Spring Expression Language)是 Spring 框架提供的表达式语言,支持在运行时查询和操作对象图。广泛用于 @Cacheable、@PreAuthorize、@Value、@ConditionalOnExpression 等注解中。
基础语法
字面量
ExpressionParser parser = new SpelExpressionParser();
parser.parseExpression("'Hello SpEL'").getValue(); // String
parser.parseExpression("3.14").getValue(); // Double
parser.parseExpression("true").getValue(); // Boolean
parser.parseExpression("null").getValue(); // null属性访问
// 对象属性(支持链式)
parser.parseExpression("name").getValue(user);
parser.parseExpression("address.city").getValue(user);
// 数组 / List 下标
parser.parseExpression("list[0]").getValue(ctx);
// Map 取值
parser.parseExpression("map['key']").getValue(ctx);方法调用
// 实例方法
parser.parseExpression("name.toUpperCase()").getValue(user);
parser.parseExpression("'hello world'.split(' ').length").getValue();
// 静态方法
parser.parseExpression("T(java.lang.Math).random()").getValue();
parser.parseExpression("T(java.lang.Integer).MAX_VALUE").getValue();运算符
// 算术
"1 + 2 * 3" // 7
"10 % 3" // 1
"2 ^ 10" // 1024(幂运算)
// 关系
"1 == 1" // true
"'abc' lt 'bcd'" // true(lt/gt/le/ge/eq/ne 文字运算符)
// 逻辑
"true and false" // false
"true or false" // true
"!false" // true
// 三元 / Elvis
"name != null ? name : 'unknown'" // 三元
"name ?: 'unknown'" // Elvis(简写)
// 安全导航(避免 NPE)
"user?.address?.city" // user 或 address 为 null 时返回 null类型操作
// instanceof
"user instanceof T(com.example.Admin)"
// 类型引用(调用静态成员)
"T(java.time.LocalDate).now()"
// 类型转换
parser.parseExpression("'100'").getValue(ctx, Integer.class)EvaluationContext
手动使用时需要提供上下文来解析变量:
StandardEvaluationContext ctx = new StandardEvaluationContext();
// 设置根对象
ctx.setRootObject(user);
// 注册变量(用 #varName 访问)
ctx.setVariable("maxAge", 60);
// 注册函数(方法引用)
ctx.registerFunction("isAdult", UserUtils.class.getDeclaredMethod("isAdult", int.class));
parser.parseExpression("#maxAge > 18").getValue(ctx);
parser.parseExpression("#isAdult(#user.age)").getValue(ctx);在注解中使用
@Value — 属性注入
@Value("#{systemProperties['user.home']}")
private String userHome;
@Value("#{T(java.lang.Math).random() * 100}")
private double randomSeed;
// 结合配置属性
@Value("${app.timeout:30}")
private int timeout;
// 引用其他 Bean 属性(注意与 ${} 区分:#{} 是 SpEL,${} 是属性占位符)
@Value("#{userConfig.maxRetry}")
private int maxRetry;详见 属性绑定。
@Cacheable — key / condition / unless
// #paramName 引用参数
@Cacheable(value = "users", key = "#id")
public User getById(Long id) { ... }
// #p0 / #a0 按参数位置引用
@Cacheable(value = "users", key = "#p0 + '-' + #p1")
public User getByNameAndTenant(String name, String tenant) { ... }
// #result 引用返回值(@CachePut / unless 中可用)
@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User findOrNull(Long id) { ... }
// #root 内置变量
@Cacheable(value = "users", key = "#root.methodName + '_' + #id")
public User getById2(Long id) { ... }| 内置变量 | 说明 |
|---|---|
#root.method | 被调用的方法对象 |
#root.methodName | 方法名 |
#root.target | 目标对象 |
#root.targetClass | 目标类 |
#root.args[0] | 第 0 个参数 |
#root.caches[0].name | 缓存名 |
#result | 方法返回值 |
详见 缓存。
@PreAuthorize / @PostAuthorize — 安全表达式
// 角色判断
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) { ... }
// 多角色
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public List<User> listAll() { ... }
// 引用方法参数
@PreAuthorize("#username == authentication.name")
public UserProfile getProfile(String username) { ... }
// 自定义 PermissionEvaluator
@PreAuthorize("hasPermission(#id, 'User', 'write')")
public void updateUser(Long id, User user) { ... }
// 后置过滤返回值列表
@PostFilter("filterObject.ownerId == authentication.principal.id")
public List<Document> listDocuments() { ... }详见 方法级安全。
@ConditionalOnExpression
@ConditionalOnExpression("'${feature.sse.enabled}' == 'true'")
@Bean
public SseEmitterManager sseEmitterManager() { ... }
// 复合条件
@ConditionalOnExpression("${feature.a} and ${feature.b}")详见 条件注解。
@Scheduled
// cron 表达式不是 SpEL,但支持 ${} 占位符
@Scheduled(cron = "${batch.cron:0 0 2 * * ?}")
public void nightlyBatch() { ... }集合操作
集合选择(Selection)
// 过滤 list 中满足条件的元素
"list.?[age > 18]" // 所有 age > 18 的元素
"list.^[age > 18]" // 第一个满足的元素
"list.$[age > 18]" // 最后一个满足的元素
// Map 过滤
"map.?[value > 100]"集合投影(Projection)
// 提取 list 中每个元素的 name 属性,返回新列表
"list.![name]"
// 投影后过滤
"list.?[age > 18].![name]"代码示例:
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.setVariable("users", userList);
// 获取所有成年用户的姓名
List<String> names = parser.parseExpression(
"#users.?[age >= 18].![name]"
).getValue(ctx, List.class);模板表达式
在字符串中嵌入 SpEL,用 #{...} 包裹:
ParserContext template = ParserContext.TEMPLATE_EXPRESSION;
String result = parser.parseExpression(
"Dear #{name}, you have #{messages.size()} messages.", template
).getValue(ctx, String.class);在 Spring Batch 中使用
Spring Batch 的条件流转支持 SpEL:
@Bean
public Job importJob() {
return jobBuilder.get("importJob")
.start(readStep())
.on("COMPLETED").to(writeStep())
.on("FAILED").to(errorStep())
.end()
.build();
}StepScope 中通过 SpEL 延迟绑定 JobParameters:
@Bean
@StepScope
public FlatFileItemReader<User> reader(
@Value("#{jobParameters['inputFile']}") String file) {
return new FlatFileItemReaderBuilder<User>()
.resource(new FileSystemResource(file))
.build();
}注意事项
#{}是 SpEL,${}是PropertyPlaceholder,两者可以嵌套:"#{'${prefix}.' + #id}"- 安全上下文(
@PreAuthorize)使用的是SecurityExpressionRoot,变量名不同于StandardEvaluationContext - SpEL 求值有性能开销,高频调用时考虑缓存
Expression对象 T()类型引用默认只能访问java.lang包,其他包需全限定类名