详解 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 表达式
参见: