SpEL 表达式

返回 Spring Boot 基础

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 包,其他包需全限定类名