详解 SpringEL(SEL)
跳到导航
跳到搜索
关于
Spring 表达式语言全称为“Spring Expression Language”,缩写为“SpEL”。 SpEL 提供一种强大、简洁的 Spring Bean 的动态操作表达式。 SpEL 表达式可以在运行期间执行,表达式的值可以动态装配到 Spring Bean 属性或构造函数中,表达式可以调用 Java 静态方法,可以访问 Properties 文件中的配置值等等。 SpringEL 能与 Spring 功能完美整合,给静态 Java 语言增加了动态功能。
SpEL 表达式
SpEL 基本表达式是由各种基础运算符、常量、变量引用一起进行组合所构成的表达式。
SpEL 支持如下表达式:
- 基本表达式:字面量表达式、关系,逻辑与算术运算表达式、字符串连接及截取表达式、三目运算及 Elivis 表达式、正则表达式、括号优先级表达式。
- 类型表达式:类型访问、静态方法/属性访问、实例访问、实例属性值存取、实例属性导航、instanceof、变量定义及引用、赋值表达式、自定义函数等等。
- 集合相关表达式:内联列表、内联数组、集合,字典访问、列表,字典,数组修改、集合投影、集合选择。
- 不支持多维内联数组初始化;不支持内联字典定义;
- 其他表达式:模板表达式。【???】
大家知道,JSP 页面的表达式使用“${}”进行声明。而 SpringEL 表达式使用“#{}”进行声明。
一般来说,SpringEL 表达式使用 #{} 进行声明。但是,不是所有注解中的 SpringEL 表达式都需要 #{} 进行声明。
- 例如:
- @Value 注解中的 SpringEL 表达式需要 #{} 进行声明;
- 而 ExpressionParser.parseExpression 实例方法中的 SpringEL 表达式不需要 #{} 进行声明;
- 另外,@CachePut 和 @Cacheable 等缓存注解中 key 属性值的 SpringEL 表达式,也不需要 #{} 进行声明。
SpEL 运算符
SpEL 提供了以下基础运算符:
- 算术运算符:加(+)、减(-)、乘(*)、除(/)、求余(%)、幂(^)、求余(MOD)和 除(DIV)等算术运算符。
- “MOD”与“%”等价,“DIV”与“/”等价,并且不区分大小写。
- 例如:“#{1+2*3/4-2}”、“#{2^3}”、“#{100 mod 9}”都是算术运算 SpEL 表达式。
- 关系运算符:等于(==)、不等于(!=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=),区间(between)运算等等。
- 例如:“#{2>3}”值为“false”。
- 逻辑运算符:与(and)、或(or)、非(!或NOT)。
- 与 Java 逻辑运算不同,SpEL 不支持“&&”和“||”。
- 例如:“#{2>3 or 4>3}”值为“true”。
- 字符串运算符:连接(+)和 截取([ ])。
- 例如:“#{'Hello'+'World!'}”的结果为“Hello World!”;“#{'Hello World!'[0]}”截取第一个字符“H”,目前只支持获取一个字符。
- 三目运算符:“逻辑表达式?表达式1:表达式2”(和 Java 一样的三目运算符)。
- 例如:“#{3>4? 'Hello':'World'}”将返回“'World'”。
- 正则表达式匹配符:matches。
- 例如:“#{'123' matches'\\d{3}'}”将返回“true”。
- 类型访问运算符:“T(Type)”。
- “Type”表示某个 Java 类型,实际上对应于 Java 类 java.lang.Class 实例;
- “Type”必须是类的全限定名(包括包名),但是核心包“java.lang”中的类除外。
- 例如:“T(String)”表示访问的是 java.lang.String 类;“#{T(String).valueOf(1)}”表示将整数 1 转换成字符串。
- 变量引用符:“#”。
- 在表达式中使用“#variableName”引用上下文变量。
SpEL 提供了一个变量定义的上下文接口—— EvaluationContext,并且提供了标准的上下文实现—— StandardEvaluationContext。 1、通过 EvaluationContext 接口的 setVariable(variableName,value) 方法,可以定义“上下文变量”,这些变量在表达式中采用“#variableName”的方式予以引用。 2、在创建变量上下文 Context 实例时,还可以在构造器参数中设置一个 rootObject 作为根,可以使用“#root”引用根对象,也可以使用“#this”引用根对象。
示例:
- 使用前面介绍的运算符定义几个 SpEL 表达式:
package com.crazymakercircle.redis.springJedis; import com.crazymakercircle.util.Logger; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.expression.EvaluationContext; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; @Component @Data public class SpElBean { /** * 算术运算符 */ @Value("#{10+2*3/4-2}") private int algDemoValue; /** * 字符串运算符 */ @Value("#{'Hello ' + 'World!'}") private String stringConcatValue; /** * 类型运算符 */ @Value("#{ T(java.lang.Math).random() * 100.0 }") private int randomInt; /** * 展示SpEl上下文变量 */ public void showContextVar() { ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = new StandardEvaluationContext(); context.setVariable("foo", "bar"); String foo = parser.parseExpression("#foo").getValue(context, String.class); Logger.info(" foo:=", foo); context = new StandardEvaluationContext("I am root"); String root = parser.parseExpression("#root").getValue(context, String.class); Logger.info(" root:=", root); String result3 = parser.parseExpression("#this").getValue(context, String.class); Logger.info(" this:=", root); } }
- 测试用例如下:
package com.crazymakercircle.redis.springJedis; //... public class SpringRedisTester { /** * 测试SpEl表达式 */ @Test public void testSpElBean() { ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring-redis.xml"); SpElBean spElBean = (SpElBean)ac.getBean("spElBean"); /** * 演示算术运算符 */ Logger.info("spElBean.getAlgDemoValue():=", spElBean.getAlgDemoValue()); /** * 演示字符串运算符 */ Logger.info("spElBean.getStringConcatValue():=", spElBean.getStringConcatValue()); /** * 演示类型运算符 */ Logger.info("spElBean.getRandomInt():=", spElBean.getRandomInt()); /** * 展示SpEL上下文变量 */ spElBean.showContextVar(); } }
缓存注解中的 SpringEL 表达式
参见: