Spring Expression Language(SpEL)
spring的一种表达式。用来动态的获取,值、对象等。
表达式语言给静态Java语言增加了动态功能
SpEL 简单的使用是通过#{}来获取等一些操作。但是不能浅显的认为#{}就是SpEL,后续我们会讲到#{}的设定。
说说常用的两种方式去设置SpEL
Annotation
注解。@Value()方便快捷。
你可以在里面方式任何符合SpEL规范的语句,并把它的返回值注入到对应的属性中。
XML
xml配置,老生常谈,经典配置。
规则
你会发行,形式是一样的。重要的是规则。来谈一下规则。
运算符
运算符类型 | 运算符 | 例子 |
---|---|---|
算术运算 | +、-、*、/、%、^ | 1+1、2^3、 |
比较运算 | <、>、==、<=、>= 、lt、 gt、 eq 、le 、ge | 1 lt 2 |
条件运算 | ?:(ternary) 、?:(Elvis) | 三元运算符 A?true : false |
逻辑运算 | and、or、not、| | A and B |
正则表达式 | matches | #{email matches ‘[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com’} |
类型判断 | instanceof | ‘xyz’ instanceof T(int) |
这里比较特殊的是mathes 他是作为运算符性质的运算。
Elivis运算符拿出来介绍一下, “表达式1?:表达式2”,从Groovy语言引入用于简化三目运算符的,当表达式1为非null时则返回表达式1,当表达式1为null时则返回表达式2
ExpressionParser parser = new SpelExpressionParser();
Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
System.out.println(city); // Smiljan
tesla.setPlaceOfBirth(null);
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
System.out.println(city); // null - does not throw NullPointerException!!!
PlaceOfBirth?.City
这里,通过?符号,增加PlaceOfBirth判断。如果PlaceOfBirth是null,则不会去获取City,从而避免程序报错。如果PlaceOfBirth是null,会直接返回null
获取方式的问题
比如一些基本类型还有string这样的引用类型的
#{1}
#{'abcd'}
#{1.0}
可以都可直接输入并返回对应的类型。又被SpEL称为字面值。
用的比较少,因为无论是用注解还是用xml都可以直接输入这些值,不需要再包装一层SpEL。
对于集合,比如List可以通过#{List[下标]}
来获取对应的Value,数组和Set集合也是一样的。Map可以同过#{Map[key]}
来获取对应的值,比如key是string类型的一个歌单,用的是歌曲名称作为key。#{歌单['歌曲名称']}
。这里没有关心返回的是什么。
从哪里获取值的问题。
bean
这个就是从其他的bean中获取值了,用这个bean的名字,通过.
来获取对应的属性。
比如一个bean,employee,有个属性是名字。
#{employee.name}
就可以获取对应的名字。
properties配置文件
可以在properties中定义属性,当然是key=value的形式。
比如在application.properties中自定义的一些属性,通过#{key}
获取对应的value。注意的是如果是自定义的配置文件,得让spring获取到它。
xml
可以直接在xml中定义properties,然后注入到对应的bean。
调用方法的问题
调用方法,要么通过对象,进行调用,要么通过类进行调用。
T()
介绍一下这个标记。通过T()可以获取对应的类,()里面放置的是类的类全限定名。比如调用系统当前时间完整的式子:
#{T(System).currentTimeMillis()}
比如获取PI
#{T(java.lang.Math).PI}
住的注意的是,调用方法是需要加上()。实际上这应该很容易记住。
获取对象
#{new java.util.Date()}
高级
自定义类、方法
自定义类,自定义方法。用#{}获取。
看到这里你可能会了解到,类或者对象进行加载的时候,是通过你给的全限定名去项目中获取的。也就是说你可以用SpEL获取你项目编写的任何类调用任何方法。
集合运算符
- .?[]
它可以对集合进行过滤,得到集合的一个子集。
用歌单为例子
#{歌单集合.?[歌手名称 eq '周杰伦']}
这样就可以获取歌单中歌手为周杰伦的歌,返回的是一个新的集合。
这里用的是阐述性的伪代码。[]中放置的是过滤的条件语句
.^[] .$[]
用来在集合中查询第一个和最后一个匹配项
#{歌单集合.^[歌手名称 eq '周杰伦']}
获取歌单中第一个歌手为周杰伦的歌
#{歌单集合.$[歌手名称 eq '周杰伦']}
获取歌单中最后一个歌手为周杰伦的歌.![]
它可以遍历集合,将集合中符合特征的子项放置到新的集合中并返回。
#{歌单集合.![歌名]}
获取到所有的歌名的集合
这的注意的是,这里的[]放置的是集合的特征属性,而不是条件。这样可以将所有的属性值提取出来。
对于这些集合运算符而言,都是集合对象去使用这些操作符,而这些操作符部分返回的又是集合,所以可以链式的使用。比如#{歌曲集合.?[歌手名称 eq '周杰伦'].![歌曲名称]}
这样就获取到了所有歌单中周杰伦的歌曲名称的集合。
SpEL非常的灵活,可以通过#{'hello world'[3]}
来获取第四个字符。也可以通过{}来直接声明一个内联的列表:
list:
{1,2,3,4}
{{'a','b'},{'x','y'}}
map:
{name:'Nikola',dob:'10 -July-1856'}
代码部分
简单的我们可以构造一个解析器(SpEL是单独模块,只依赖于core模块,不依赖于其他模块,可以单独使用)。
从helloworld开始
package cn.javass.spring.chapter5;
import junit.framework.Assert;
import org.junit.Test;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class SpELTest {
@Test
public void helloWorld() {
ExpressionParser parser = new SpelExpressionParser();
Expression expression =
parser.parseExpression("('Hello' + ' World').concat(#end)");
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("end", "!");
Assert.assertEquals("Hello World!", expression.getValue(context));
}
}
1. 创建解析器:SpEL使用ExpressionParser接口表示解析器,提供SpelExpressionParser默认实现;
2. 解析表达式:使用ExpressionParser的parseExpression来解析相应的表达式为Expression对象。
3. 构造上下文:准备比如变量定义等等表达式需要的上下文数据。(可选)
4. 求值:通过Expression接口的getValue方法根据上下文获得表达式值。
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length");
int length = (Integer) exp.getValue();
这样可以获取到’Hello World’的长度
使用构造器:
ExpressionParser parser = new SpelExpressionParser();
Expression exp =
parser.parseExpression("new String('hello world').toUpperCase()");
String message = exp.getValue(String.class);
看一下这个例子
// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);
// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
EvaluationContext context = new StandardEvaluationContext(tesla);
String name = (String) exp.getValue(context);
在最后一行代码中,变量name
会被设置成”Nikola Tesla”,StandardEvaluationContext 类用来指定哪一个对象的name属性会被重新设定。于是,装载到StandardEvaluationContext 类中的Inventor的name属性被替换为”Nikola Tesla”。输出Nikola Tesla。
这里使用一个标准化的上下文。
如下代码:
// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("primes",primes);
// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
"#primes.?[#this>10]").getValue(context);
#this引用当前的评估对象
这两块都用到了StandardEvaluationContext,实际上就是一个标准上下文计算的容器。我们给StandardEvaluationContext设置primes属性,这样表达式根据上下文,就可以直接的使用这个声明的变量。再上面的例子放入了一个类的实例Inventor,这样当表达式去声明name的时候,就可以直接在这个容器里装载的对象里获取到name属性的值Nikola Tesla。
正常的话应该,new 类的全路径().name来获取。将变量放置在上下文的容器里,表达式就能直接的调用了。这也就是StandardEvaluationContext的作用。这样我们就可以通过将一下自定义的类放置进去,然后直接获取对应的属性或者方法了。
实际上,在给一些@Value操作中,我们也经常会将其他的bean的一些属性注入到当前bean属性中。内部也是通过这样的方法。
针对上面讲述的#{},可以看看:
String randomPhrase = parser.parseExpression(
"random number is #{T(java.lang.Math).random()}",
new TemplateParserContext()).getValue(String.class);
// evaluates to "random number is 0.7038186818312008"
TemplateParserContext类的内容是:
public class TemplateParserContext implements ParserContext {
public String getExpressionPrefix() {
return "#{";
}
public String getExpressionSuffix() {
return "}";
}
public boolean isTemplate() {
return true;
}
}
这里自定义了分隔符delimiters,使用#{最为前缀,}作为后缀。
回头看”random number is #{T(java.lang.Math).random()}”,也就是内部实际上是通过传入定义类TemplateParserContext 来接解析文本”random number is #{T(java.lang.Math).random()}”,当然,默认和更加通用是使用#{}。
更多内容参见Spring官网对于SpringEL的介绍
大佬写的我觉得非常好的博文,SpEL的