Spring表达式语言(Spring Expression Language)简称:SpEL
课程概要:
- Spring表达式语言的入门介绍
- Spring表达式语言的操作范围
- Spring表达式语言的运算符
- Spring表达式语言的集合操作
一.Spring表达式语言入门级介绍
1.基本概述
Spring表达式语言全称为“Spring Expression Language”,缩写为“SpEL”,他能在运行时构建复杂表达式、存取对象属性、对象方法调用等等,并且能与Spring功能完美整合。表达式语言给静态Java语言增加了动态的功能,表达式语言是单独的模块,他只依赖与核心的模块,不依赖与其他模块,能够单独的使用。表达式语言通常是以最简单的形式完成最复杂的工作来减少我们的工作量,Spring语言主要支持如下的表达式。
- 基本表达式
- 类相关表达式
- 集合相关表达式
- 其他表达式
注:Spring的表达式不区分大小写
2.示例分析
public class SpelTest { public static void main(String[] args){ //创建解析器 ExpressionParser parser=new SpelExpressionParser(); //解析表达式 Expression expression= parser.parseExpression("('Hello'+'World').concat(#end)"); //构造上下文 EvaluationContext context=new StandardEvaluationContext(); //为end参数值来赋值 context.setVariable("end","!"); //打印expression表达式的值 System.out.println(expression.getValue(context)); } }
3.工作原理
在介绍Spring表达式语言工作原理之前,先介绍一下一些基本概念:
- 表达式:表达式语言的核心,即“干什么”
- 解析器:用于将字符串表达式解析为表达式对象,即“谁来干”
- 上下文:表达式语言执行的环境,该环境可能定义变量,可能定义自定义函数,也可以提供类型转换等等,即“在哪里干”
- 根对象即活动上下文对象:根对象是默认的活动上下文对象,活动上下文对象表示了当前操作对象。即“对谁干”
接下来让我们来看一下Spring是如何工作的:
1.首先需要定义一个表达式
2.然后得定义解析器ExpressionParser,Spring语言提供了默认的实现即SpelExpressionParser。
①SpelExpressionParser解析器内部进行词法分析,即把字符串流分析为记号流。记号在SpEL当中使用
类
来进行表示。
②有了记号流之后,解析器便可根据记号流生成内部抽象语法树。在SpEL当中,语法树节点使用SpelNode接口进行实现。
③对外提供Expression接口来简化抽象语法树。从而隐藏内部的实现细节。并提供getValue()方法用于获取表达式。
3.下一步定义上下文对象,这一步是可选的。SpEL使用EvaluationContext接口来表示上下文对象。他主要用于设置根对象,自定义变量、自定义函数、类型转换器等等。SpEL提供的默认实现即为StandardEvaluationContext
4.最后一步是根据表达式来求值,即调用表达式getValue方法来获得最终的结果。
接下来看以下SpEL的主要接口
- ExpressionParser接口:表示解析器
- EvaluationContext接口:表示上下文环境
- Expression接口:表示的是表达式对象
4.配置风格
以上是使用Java语言配置Spring表达式语言,
接下来我们使用XML来配置。
XML风格的配置:
SpEL支持在Bean定义时注入,默认使用“#{SpEL表达式}”表示,其中“#root”根对象默认可以认为是ApplicationContext,只有ApplicationContext实现默认支持SpEL,获取根对象属性其实是获取容器中的Bean
我们来看一个示例:
首先创建一个配置文件,在配置文件中使用Spring表达式语言创建Bean
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <bean id="world" class="java.lang.String"> <constructor-arg value="#{' World!'}"/> </bean> <!--方式一--> <bean id="hello1" class="java.lang.String"> <constructor-arg value="#{'Hello '}#{world}"/> </bean> <!--方式二 注意:Spring语言不支持嵌套,即在一个#之内又有一个# <constructor-arg value="#{'Hello '#{world}}"/>--> <bean id="hello2" class="java.lang.String"> <constructor-arg value="#{'Hello '+world}"/> </bean> <!--方式三--> <bean id="hello3" class="java.lang.String"> <constructor-arg value="#{'Hello '+@world}"/> </bean> </beans>
可以看到,我们使用了三种使用Spring表达式语言的方法来在配置文件中配置bean的参数。
接下来我们创建一个测试类来测试下各个bean的值
public class XmlExpression { public static void main(String[] args){ ApplicationContext ctx= new FileSystemXmlApplicationContext("src/conf/conf-spel.xml"); String hello1=ctx.getBean("hello1",String.class); String hello2=ctx.getBean("hello2",String.class); String hello3=ctx.getBean("hello3",String.class); System.out.println(hello1); System.out.println(hello2); System.out.println(hello3); } }
另外一种配置Spring表达式语言的方法便是注解方式。
注解风格的配置:
基于注解风格的SpEL配置也非常简单,使用@Value注解来指定SpEL表达式,该注解可以放到字段、方法以及方法参数上。
我们使用示例来演示以下,首先修改配置文件
<!--开启注解支持--> <context:annotation-config/> <bean id="hellobean1" class="cn.lovepi.chapter05.spel.AnnoExpression"/> <bean id="hellobean2" class="cn.lovepi.chapter05.spel.AnnoExpression"> <property name="value" value="haha"/> </bean>
声明了两个bean,其中一个使用属性注入的方式注入了特定的参数。由于使用了注解,所以得在配置文件中开启注解支持。
接下来编写对应的java代码
public class AnnoExpression { @Value("#{'Hello '+world}") private String value; public String getValue() { return value; } public void setValue(String value) { this.value = value; } public static void main(String[] args){ ApplicationContext ctx= new FileSystemXmlApplicationContext("src/conf/conf-spel.xml"); AnnoExpression hellobean1=ctx.getBean("hellobean1",AnnoExpression.class); AnnoExpression hellobean2=ctx.getBean("hellobean2",AnnoExpression.class); System.out.println(hellobean1.getValue()); System.out.println(hellobean2.getValue()); } }
通过结果可以看出:使用参数注入方式注入的值会覆盖Spring表达式所编写的值
二.Spring表达式语言的操作范围
SpEL表达式的首要目标是通过计算获得某个值,在计算这个值的过程中,会使用到其他的值并会对这些值进行操作,值的操作范围如下:
- 字面值:最简单的一种值,即基本类型的表达式。包含的类型是字符串、数字类型(int、lang、float、double、boolean、null)字符串使用单引号分割,使用反斜杠字符转义。
- Bean以及Bean的属性或方法:通过id来引入其他的bean或者bean的属性或方法
- 类的方法和常量:在SpEL中是由T运算符调用类的方法和常量
最简单的SpEL表达式仅包含一个简单的字面值
我们创建一个Bean类来演示一下:
public class SpelLiteral { private int count; private String message; private float frequency; private float capacity; private String name1; private String name2; private boolean enabled; public int getCount() { return count; } public void setCount(int count) { this.count = count; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public float getFrequency() { return frequency; } public void setFrequency(float frequency) { this.frequency = frequency; } public float getCapacity() { return capacity; } public void setCapacity(float capacity) { this.capacity = capacity; } public String getName1() { return name1; } public void setName1(String name1) { this.name1 = name1; } public String getName2() { return name2; } public void setName2(String name2) { this.name2 = name2; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } }可以看到该Bean有很多基本数据类型以及String类型的属性,接下来我们一一在配置文件中为这些属性使用Spring表达式语言赋值
<bean id="spelliteral" class="cn.lovepi.chapter05.spel.SpelLiteral"> <property name="count" value="#{5}"/> <property name="message" value="The value is #{5}"/> <property name="frequency" value="#{89.7}"/> <property name="capacity" value="#{1e4}"/> <property name="name1" value="#{'wang'}"/> <property name="name2" value='#{"wang"}'/> <property name="enabled" value="#{false}"/> </bean>
在这里我们使用了property属性注入的方式来为Bean的属性注入参数,可以看到使用Spring表达式语言可以表示多种类型的值。
接下来我们创建个测试类来测试下是否将值正确的注入到Bean当中去。
public class SpelMain { public static void main(String[] args){ testSpelLiteral(); } private static void testSpelLiteral(){ ApplicationContext ctx= new FileSystemXmlApplicationContext("src/conf/conf-spel.xml"); SpelLiteral literal=ctx.getBean("spelliteral",SpelLiteral.class); System.out.println("count= "+literal.getCount()); System.out.println("message= "+literal.getMessage()); System.out.println("frequency= "+literal.getFrequency()); System.out.println("capacity= "+literal.getCapacity()); System.out.println("name1= "+literal.getName1()); System.out.println("name2= "+literal.getName2()); System.out.println("enabled= "+literal.isEnabled()); } }
输出结果为:
count= 5
message= The value is 5
frequency= 89.7
capacity= 10000.0
name1= wang
name2= wang
enabled= false
SpEL表达式所能做到的另外一个事情便是通过id来引用其他Bean。包括Bean本身,Bean的属性以及Bean的方法。
SpEL引用Bean本身
<property name="bean2" value="#{bean1}"/>这句话等价与
<property name="bean2" ref="bean1"/>
可以看到使用SpEL表达式并不如直接使用ref标签来引用其他Bean来的方便,但SpEL在下面的使用体验可就非常棒了。
SpEL引用Bean的属性
以上的代码等价于
<bean id="bean2" class="cn.lovepi.***"> <property name="name" value="#{bean1.name}"/> </bean>
以上的代码等价于
Bean2 bean2=new Bean2(); bean2.setName(bean1.getName());
可以看到使用Spring表达式语言可以更方便的获取Bean的属性
SpEL引用Bean的方法
获取bean1的name值将其赋值给bean2的属性中
<property name="name" value="#{bean1.getName()}/>还可以将获取到的name值转换为大写
<property name="name" value="#{bean1.getName().toUpperCase()}/>
但是这种情况只能在getName方法不返回空值的情况下,假如getName返回空值的话则会抛出空指针异常。
在SpEL中,为了避免空指针异常可以使用如下的方法:
<property name="name" value="#{bean1?.getName().toUpperCase()}/>
在这里我们使用使用“?.”运算符来代替“.”运算符,这样可以确保在左边不为空的情况下才执行右边的方法,否则将不执行。
接下来我们使用示例来演示下,首先我们创建一个Java Bean,其中包括两个float的属性
public class SpelClass { private float pi; private float randomNumber; public float getPi() { return pi; } public void setPi(float pi) { this.pi = pi; } public float getRandomNumber() { return randomNumber; } public void setRandomNumber(float randomNumber) { this.randomNumber = randomNumber; } }为其编写配置文件
<bean id="spelClass" class="cn.lovepi.chapter05.spel.SpelClass"> <property name="pi" value="#{T(java.lang.Math).PI}"/> <property name="randomNumber" value="#{T(java.lang.Math).random()}"/> </bean>
可以看到我们使用SpEL使用了Math类的属性PI和方法random()。
让我们测试一下
private static void testSpelClass(){ ApplicationContext ctx= new FileSystemXmlApplicationContext("src/conf/conf-spel.xml"); SpelClass spelClass=ctx.getBean("spelClass",SpelClass.class); System.out.println("PI="+spelClass.getPi()); System.out.println("randomNumber="+spelClass.getRandomNumber()); }
可以看到最后结果为:
PI=3.1415927
randomNumber=0.541514
三.Spring表达式语言的运算符
上面我们介绍了Spring表达式语言所能操作的值的范围,接下来我们来学习下如何来操作这些值,即SpEL的运算符。
运算符类型 |
运算符示例
|
数值运算 | +、-、*、/、%、^(乘方运算) |
比较运算 | <(lt)、>(gt)、==(eg)、<=(le)、>=(ge) |
逻辑运算 | and、or、not、| |
条件运算 | ?:(ternary)、?:(Elvis) |
正则表达式 | matches |
接下来我们分别对这些运算符进行介绍
1.数值运算
数值运算符可以对SpEL表达式中的值进行基础数学运算
接下来我们来示例演示一下,首先先创建一个基本Bean用来存放待运算的数据信息
public class SpelCounter { private float total; private float count; public float getTotal() { return total; } public void setTotal(float total) { this.total = total; } public float getCount() { return count; } public void setCount(float count) { this.count = count; } }接下来创建一个运算演示Bean
public class SpelMath { private float ajustedAcount; private float circumFference; private float average; private float remainder; private float area; private String fullName; public float getAjustedAcount() { return ajustedAcount; } public void setAjustedAcount(float ajustedAcount) { this.ajustedAcount = ajustedAcount; } public float getCircumFference() { return circumFference; } public void setCircumFference(float circumFference) { this.circumFference = circumFference; } public float getAverage() { return average; } public void setAverage(float average) { this.average = average; } public float getRemainder() { return remainder; } public void setRemainder(float remainder) { this.remainder = remainder; } public float getArea() { return area; } public void setArea(float area) { this.area = area; } public String getFullName() { return fullName; } public void setFullName(String fullName) { this.fullName = fullName; } }
在配置文件中使用SpEL表达式语言运算符来对相应的数据进行运算赋值操作
<bean id="spelCounter" class="cn.lovepi.chapter05.spel.SpelCounter"> <property name="count" value="#{10}"/> <property name="total" value="#{100}"/> </bean> <bean id="spelMath" class="cn.lovepi.chapter05.spel.SpelMath"> <!--加法运算符--> <property name="ajustedAcount" value="#{spelCounter.total+53}"/> <!--乘法运算符--> <property name="circumFference" value="#{2*T(java.lang.Math).PI*spelCounter.total}"/> <!--除法运算符--> <property name="average" value="#{spelCounter.total/spelCounter.count}"/> <!--取余运算符--> <property name="remainder" value="#{spelCounter.total%spelCounter.count}"/> <!--乘方运算符--> <property name="area" value="#{T(java.lang.Math).PI * spelCounter.total^2}"/> <!--字符串拼接--> <property name="fullName" value="#{'icarus'+' '+'wang'}"/> </bean>接下来在程序入口出测试程序运行结果
private static void testSpelMath(){ ApplicationContext ctx= new FileSystemXmlApplicationContext("src/conf/conf-spel.xml"); SpelMath math=ctx.getBean("spelMath",SpelMath.class); System.out.println("AjustedAcount= "+math.getAjustedAcount()); System.out.println("CircumFference= "+math.getCircumFference()); System.out.println("Average= "+math.getAverage()); System.out.println("Area= "+math.getArea()); System.out.println("Remainder= "+math.getRemainder()); System.out.println("FullName= "+math.getFullName()); }
可以看到程序的运行结果为:
AjustedAcount= 153.0
CircumFference= 628.31854
Average= 10.0
Area= 31415.926
Remainder= 0.0
FullName= icarus wang
2.比较运算
SpEL表达式同样提供Java所支持的比较运算符,但为了适应XML的配置规则,SpEL提供了文本型比较运算符
3.逻辑运算符
逻辑运算符用于对两个比较表达式进行求值,或者对某些布尔类型的值进行非运算,下表列出了SpEL当中的所有逻辑运算符
4.条件运算
当某个条件为true时,SpEL的表达式的求值结果是某个值;如果该条件为false时,它到的求值结果是另一个值时,可以使用SpEL的三元运算符。(?:):
SpEL的三元运算符的使用和Java相同,其主要作用是判断一个值是否为null,并对其进行处理,如下所示:
<property name="name" value="#{person.name!=null ? person.name : 'icarus'}"/>但上面的的语句重复使用了两次person.name属性,SpEL为我们提供了一种更简便的方式:
<property name="name" value="#{person.name!=null ?: 'icarus'}"/>
这个语句的效果和上面的是相同的。
5.正则表达式
当处理文本时,检查文本是否匹配某种模式有时是非常有用的。SpEL通过matches运算符支持表达式中的模式匹配。如果匹配则返回true,不匹配则返回false。
假如我们想要对一个邮件地址的字符串进行判断,那么我们则可以按照如下配置:
四.Spring表达式语言的集合操作
<property name="validEmail" value="#{admin.email matches '[0-9A-Za-z_%.*+-]+@[0-9A-Za-z.-]+\\.com'}"/>
四.Spring表达式语言的集合操作
SpEL可以引用集合中的某个成员,就像在Java里操作一样,同样具有基于属性值来过滤集合成员的能力。SpEl对集合的操作主要包括以下几种:
- 访问集合成员
- 查询集合成员
- 投影集合
1.访问集合元素
为了展示SpEL访问集合成员的用途,需要定义一个SpelCity类,然后使用<util:list>元素在Spring里配置一个包含SpelCity对象的List集合,示例如下:
我们首先定义一个SpelCity类,如下所示:
public class SpelCity{ private String name; private String state; private int population; }接下来我们创建一个集合,集合中的元素是SpelCity
<util:list id="cities"> <bean class="cn.lovepi.***.SpelCity"> <p:name="Chicago" p:state="IL" p:population="2853114"> <bean class="cn.lovepi.***.SpelCity"> <p:name="LasCryces" p:state="NM" p:population="91865"> </util:list>
2.查询集合成员
接下来我们演示利用SpEL来查询集合成员
如果我们想从cities集合当中查询人口多余十万的城市,
那么一种实现方式是将所有的city Bean都装配到Bean的属性当中,然后在该Bean中增加过滤不符合条件的城市。
在SpEL表达式语言当中使用查询运算符“.?[]”即可实现以上功能。如下所示:
<property name="bigCities" value="#{cities.?[population gt 100000]}"/>
查询运算符会创建一个新的集合,新的集合当中只存放符合中括号内的值。
SpEL同样提供了两种其他的查询运算符
- .^[]:查询符合条件的第一个元素
- .$[]:查询符合条件的最后一个元素
3.集合投影
集合投影是从集合的每一个成员中选择特定的属性放入一个新的集合当中。SpEL的投影运算符(.![])完全可以做到这点。
假如我们需要将cities集合当中的所有name属性注入到一个新的集合当中,那么我们可以这样:
<property name="cityName1" value="#{cities.![name]}}"/>
投影不局限与投影单一的属性,如下所示:
将cities集合中的名称和简称都投影出来
<property name="cityName2" value="#{cities.![name+','+state]}}"/>
当然还可以对集合进行查询和投影的双重运算:
将大城市的
名称和简称都投影出来
<property name="cityName3" value="#{cities.?[population gt 100000].![name+','+state]}}"/>
总结:
虽然SpEL表达式语言非常强大,但是SpEL表达式语言只是一个字符串,并没有id之类的编译支持,所以并不建议深入学习SpEL表达式,只需了解知道即可。