Spring中的SpEL是什么
一句话:SpEL(Spring Expression Language)是 Spring 自带的“运行时表达式语言”,用来在 XML、注解、代码 里动态取值、运算、调用方法,功能对标 OGNL / EL,但语法更简洁、与 Spring 容器深度集成。
1. 能做什么(直接举例)
场景 | 示例 | 结果 |
---|---|---|
注入配置文件里的值 | @Value("#{systemProperties['user.region']}") | 取 JVM 启动参数 |
引用其他 Bean 的属性 | @Value("#{orderService.nextOrderId}") | 实时拿到 orderService 的字段 |
调用静态方法 | @Value("#{T(java.lang.Math).random()}") | 0~1 随机数 |
集合过滤 / 投影 | @Value("#{users.?[age > 18]}") | 返回成年用户列表 |
条件判断 | @Value("#{profile == 'prod' ? 8080 : 9000}") | 动态端口 |
2. 语法速记(30 秒写完)
#{ } 表达式定界符
T(全限定类) 访问静态类/方法
bean.property 访问 Bean 属性
bean.method() 调用 Bean 方法
list[0] 索引
map['key'] Map 取值
.?[ ] .![ ] 过滤 / 投影
3. 运行原理(一句话背)
SpEL 被解析成 AST → EvaluationContext → 反射执行,Spring 默认把容器注册成
BeanFactoryResolver
,所以表达式里可以直接写 Bean 名。
4. 常见坑
表达式拼错启动不报错
@Value("#{nosuchBean.xxx}")
只有在第一次注入时才会抛SpelEvaluationException
,属于“运行时异常”,单元测试容易漏掉。类型自动拆装箱失败
@Value("#{1}")
注入到int
可以,注入到Integer
没问题;但注入到boolean
会抛ClassCastException
,需要显式#{1 != 0}
。与占位符
${}
混用${}
只能读 属性源(application.yml、环境变量)。#{}
可以读 Bean、系统属性、运算。
错误示例:@Value("${systemProperties['user.region']}")
→ 应改为#{}
。
集合投影后类型丢失
#{users.![name]}
返回List<String>
,但注入到List<User>
会报类型不匹配,需要显式强转或泛型擦除处理。性能陷阱
表达式每次注入都会重新解析;在 循环或高并发 场景,建议把结果缓存到局部变量,或使用Expression
预编译:Expression exp = parser.parseExpression("price * discount");
5. 实战 3 连击
动态路由数据源
@Bean @Scope("prototype") public DataSource dataSource(@Value("#{dsProps.urls[T(ThreadLocalRandom).current().nextInt(dsProps.urls.size())]}") String url) {return new DriverManagerDataSource(url, user, pwd); }
AOP 切点里用 SpEL
@Around("@annotation(rateLimit) && args(id)") public Object limit(ProceedingJoinPoint pjp, RateLimit rateLimit, Long id) {String key = parser.parseExpression(rateLimit.key()).getValue(context, id, String.class);... }
Thymeleaf 模板
<span th:text="${@userService.findById(#vars.userId).name}"></span>
6. 一句话总结
SpEL 就是 Spring 的“运行时脚本”,能在注解、XML、模板里直接读写 Bean、环境变量、集合、静态方法,语法简洁、功能强大,但要警惕运行时异常和性能开销。