[Spring实战系列](11)SpEL使用表达式装配

  1. 云栖社区>
  2. 博客>
  3. 正文

[Spring实战系列](11)SpEL使用表达式装配

sjf0115 2016-02-05 19:39:06 浏览302
展开阅读全文
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SunnyYoona/article/details/50638957
到目前为止,我们为Bean 的属性和构造器参数装配的所有东西都是在Spring 的XML 配置文件中静态定义的。

<bean id = "yoona" class = "com.sjf.bean.Student">
<property name="name" value = "yoona"/>
<property name="hobby" value = "踢足球,打羽毛球" />
</bean>

上述代码定义了一个Student的Bean,他的爱好就是"踢足球,打羽毛球",这些内容我们在开发期就确定了。要是他的兴趣爱好改变了,我们又如何操作呢?同样,当我们装配其他Bean 的引用时,这些引用同样是在我们编写Spring 配置文件时就已经确定了。我们前期所有的操作都是基于XML配置文件中静态定义的。

但是,如果我们为属性装配的值只有在运行期才能知道,那又如何实现呢?

Spring 3 引入了Spring 表达式语言(Spring Expression Language,SpEL)。SpEL是一种强大、简洁的装配Bean 的方式,它通过运行期执行的表达式将值装配到Bean的属性或构造器参数中。使用SpEL,可以实现超乎想象的装配效果,这是使用传统的Spring 装配方式难以做到的(甚至是不可能的)。

SpEL 拥有许多特性,包括:

  • 使用Bean 的ID 来引用Bean ;
  • 调用方法和访问对象的属性;
  • 对值进行算术、关系和逻辑运算;
  • 正则表达式匹配;
  • 集合操作。

编写SpEL 表达式需要拼凑各种SpEL 语法的元素,即便是最有趣的SpEL 表达式通常也是由简单的表达式组成。所以在开始使用SpEL 之前,让我们首先了解一下SpEL 表达式中一些最基本的要素。

#{ } 标记会提示Spring 这个标记里的内容是SpEL 表达式。

1. 字面值

我们可以在<property> 元素的value 属性中使用#{} 界定符把这个值装配到Bean 的属性中,如下例:

<bean id = "yoona" class = "com.sjf.bean.Student">
<property name="age" value="#{24}"/>
<property name="school" value="#{'西电'}"/>
<property name="name" value="my name is #{'yoona'}"/>
<property name="weight" value="#{120.4}"/>
<property name="sex" value="#{true}"/>
</bean>
第一个SpEL表达式中使用了一个整型:#{24}
第二个SpEL表达式中使用了String类型:#{'西电'}  
注意:里面有个单引号,如果去掉会报错。String 类型的字面值可以使用单引号或双引号作为字符串的界定符。如果使用单引号作为XML 属性的界定符则:<propertyname='school'value='#{"西电"}'/>
第三个SpEL表达式中使用了String类型,并且与非SpEL 表达式的值混用:my name is #{'yoona'}
第四个SpEL表达式中使用了浮点型数字:#{120.4},也可以采用科学计数法的表示方法:#{'1e4'}   
第五个SpEL表达式中使用布尔值:"#{true}"

运行结果:

name:my name is yoona age:24 school:西电 sex:true weight:120.4

感觉杀鸡焉用牛刀,毕竟,我们没必要使用SpEL 将一个整型的属性赋值为24,或者将Boolean 类型的属性赋值为true。我承认在SpEL 表达式中仅包含字面值没有太多用处。但是复杂的SpEL 表达式通常是由简单的表达式构成的。所以了解如何在SpEL 中使用字面值是很有意义的。

2.  引用Bean、Properties 和方法

2.1 调用引用Bean的属性

SpEL 表达式能做的另一个事情是通过ID 引用其他Bean。举个例子,我们需要在SpEL 表达式中使用Bean ID 将一个Bean 装配到另一个Bean 的属性中:

<property name="school" value="#{xidianSchool}"/>
上述代码中的SpEL 表达式相当于:

<property name="school" ref="xidianSchool"/>

没错,结果是相同的,我们使用SpEL也没感觉出便利多少。但是有一点是ref做不到的,就是在一个SpEL 表达式中使用Bean 的引用来获取Bean 的属性。

<property name="address" value="#{xidianSchool.location}"/>
假设一个Student的address(地址)属性跟School(学校)的location(地址)是一样的,都是"西安"的,那我们可以引用School的属性location为Student的属性address赋值。

第一部分(在句号分割符之前的部分xidianSchool)通过其ID 指向xidianSchool Bean。第二部分指向xidianSchool Bean 的location 属性。通过这种方式装配address属性,其实等价于执行下面的示例代码:

Student student = new Student();
student.setAddress(xidianSchool.getLocation());

2.2 调用引用Bean的方法

我们不只可以调用引用Bean的属性,还可以调用引用Bean的方法假设xidianSchool Bean有个getLocation()方法

<property name="address" value="#{xidianSchool.getLocation()}"/>

2.3 null-safe 存取器避免空指针异常

假设有SpEL中有xidianSchool.getLocation().getCity(),并且 如果getLocation() 返回一个null 值, 那么SpEL 表达式求值时会抛出一个NullPointerException 异常。那么避免抛出空指针异常(NullPointerException)的方法是使用null-safe 存取器(?.):

<property name="address" value="#{xidianSchool.getLocation()?.getCity()}"/>
现在我们使用?. 运算符代替点(.)来访问getCity() 方法。在访问右边方法之前,该运算符会确保左边项的值不会为null。所以,如果getLocation()返回null 值,SpEL 不再尝试调用getCity() 方法。

3. 操作类

在SpEL 中,使用T() 运算符会调用类作用域的方法和常量。例如,在SpEL 中使用Java 的Math 类,我们可以像下面的示例这样使用T() 运算符:

T(java.lang.Math)

在上面示例中,T() 运算符的结果会返回一个java.lang.Math 的类对象。但是,T()运算符真正的价值在于,通过该运算符可以访问指定类的静态方法和常量

假设需要把PI 的值装配到Bean 的一个属性中。只需简单引用Math 类的PI 常量即可,如下所示:

<property name="pi" value="#{T(java.lang.Math).PI}"/>

同样,使用T() 运算符也可以调用静态方法。

<property name="randomNumber" value="#{T(java.lang.Math).random()}"/>

4. 在SpEL值上执行操作

SpEL 提供了几种运算符,这些运算符可以用在SpEL 表达式中的值上。
运算符类型 运算符
算术运算 +, -, *, /, %, ^
关系运算 Li YanHong
逻辑运算 < ,>,==,<=,>=,lt,gt,eq,le,ge
条件运算 and,or,not,|
正则表达式 ?:(ternary),?:(Elvis)
Nokia matches

4.1 算术运算

SpEL 提供了所有Java 支持的基础算术运算符,它还增加了(^)运算符来执行乘方运算

(1)加法,这里我们把counter Bean 的total 属性值与42 相加。

<property name="adjustedAmount" value="#{counter.total + 42}"/>
(2)减法,这里我们把counter Bean 的total 属性值与20 相减。

<property name="adjustedAmount" value="#{counter.total - 20}"/>
(3)乘法,这里我们计算一个圆的周长。

<property name="circumference" value="#{2 * T(java.lang.Math).PI * circle.radius}"/>
(4)除法,这里我们把counter Bean 的total 属性值与 count 属性值相除。

<property name="average" value="#{counter.total / counter.count}"/>
(5)求余

<property name="remainder" value="#{counter.total % counter.count}"/>
(6)乘方,不同于Java ,SpEL提供了乘方运算,这里求圆的面积。

<property name="area" value="#{T(java.lang.Math).PI * circle.radius ^ 2}"/>
(7)+不只提供加法运算,还提供了字符串连接的功能。

<property name="fullName" value="#{student.firstName + ' ' + student.lastName}"/>

4.2 关系运算

判断两个值是否相等或者两者之间哪个更大,这种情况很常见。对于这种类型的比较,SpEL 同样提供了Java 所支持的比较运算符。
(1)相等,这里equal属性为布尔类型。如果age等于24则将会true装配给equal属性。

<property name="equal" value="#{student.age == 24}"/>
(2)类似地,小于(<)和大于(>)运算符用于比较不同的值。而且SpEL 还提供了大于等于(>=)和小于等于(<=)运算符。不过,在Spring 的XML 配置文件中使用小于等于和大于等于符号时,会报错,这是因为这两个符号在XML 中有特殊含义。当在XML 中使用SpEL时,最好对这些运算符使用SpEL 的文本替代方式。

<property name="hasCapacity" value="#{counter.total le 100000}"/>
其实相当于(其实这样是不正确的,只是演示而用,明显看出代码高亮显示不对):

<property name="hasCapacity" value="#{counter.total <= 100000}"/>

文本型比较运算符如下表:
运算符 符号 文本类型
等于 == eq
小于 < lt
小于等于 <= le
大于 > gt
大于等于 >= ge

即使等于运算符(==)在XML 文件中不会产生问题,但是SpEL 还是提供了文本型的eq 运算符,从而与其他运算符保持一致性。这是因为某些开发者更倾向于使用文本型运算符,而不是符号型运算符。

4.3 逻辑运算

SpEL 提供了多种运算符,你可以使用它们来对表达式进行求值:
运算符 操作
and 逻辑AND运算操作,只有运算符两边都是true,表达式才能是true。
or 逻辑OR运算操作,只有运算符任意一边是true,表达式就会是true。
not 或 ! 逻辑NOT运算操作,对运算结果求反。

(1)在这个示例中, 如果xiaosiStudent Bean 的name 属性为xiaosi, 并且age属性的值大于24,则isHer属性将被设为true,否则为false。

<property name="isHer" value="#{xiaosiStudent.age gt 24 and xiaosiStudent.name == 'xiaosi'}"/>
(2)使用not运算符

<property name="otherStudent" value="#{not xiaosiStudent.sex}"/>

4.4 条件运算

如果我们希望在某个条件为true 时,SpEL 表达式的求值结果是某个值;如果该条件为false 时,它的求值结果是另一个值,那么这要如何实现呢?使用?:符号。
(1)如果yoonaStudent Bean的sex属性为true,则把boy装配给sex属性,否则装配girl。

<property name="sex" value="#{yoonaStudent.sex == true ? 'boy' : 'girl'}"/>
(2)如果kenny.song 不为null,那么表达式的求值结果是kenny.song,否则就是“Greensleeves”。当我们以这种方式使用时,“?:”通常被称为elvis 运算符。

<property name="song" value="#{kenny.song ?: 'Greensleeves'}"/>

4.5 正则表达式

matches 运算符对String 类型的文本(作为左边参数)应用正则表达式(作为右边参数)。matches 的运算结果将返回一个布尔类型的值:如果与正则表达式相匹配,则返回true ;否则返回false。
假设我们想判断一个字符串是否是有效的邮件地址。

<property name="validEmail" value= "#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}"/>

5. 在SpEL 中筛选集合

我们可以引用集合中的某个成员,就像在Java 里操作一样,同样具有基于属性值来过滤集合成员的能力,还可以从集合的成员中提取某些属性放到一个新的集合中。

注意:

使用<util:list>需要添加xmlns:util="http://www.springframework.org/schema/util" 和
 
xsi:schemaLocation=" http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"。
我们使用<util:list> 元素在Spring 里配置了一个包含School 对象的List 集合(学校集合):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:list id = "schools">
<bean class="com.sjf.bean.School">
<property name="name" value="西安电子科技大学"/>
<property name="location" value="西安"></property>
</bean>
<bean class="com.sjf.bean.School">
<property name="name" value="西安交通大学"/>
<property name="location" value="西安"></property>
</bean>
<bean class="com.sjf.bean.School">
<property name="name" value="西北工业大学"/>
<property name="location" value="西安"></property>
</bean>
<bean class="com.sjf.bean.School">
<property name="name" value="山东大学"/>
<property name="location" value="山东"></property>
</bean>
<bean class="com.sjf.bean.School">
<property name="name" value="山东科技大学"/>
<property name="location" value="山东"></property>
</bean>
<bean class="com.sjf.bean.School">
<property name="name" value="北京大学"/>
<property name="location" value="北京"></property>
</bean>
<bean class="com.sjf.bean.School">
<property name="name" value="上海交通大学"/>
<property name="location" value="上海"></property>
</bean>
</util:list>
<bean id = "yoonaStudent" class = "com.sjf.bean.Student">
<property name="name" value="yoona"/>
<property name="age" value="24"/>
<property name="school" value="#{schools[2]}"/>
</bean>
</beans>


<util:list> 元素是由Spring 的util 命名空间所定义的。它创建了一个java.util.List 类型的Bean。在这种场景下,它是一个包含7 个School 学校 的List 集合。

Student类:

package com.sjf.bean;
 
import java.util.Collection;
 
/**
* Student实体类
* @author sjf0115
*
*/
public class Student {
public String name;
public int age;
public School school;
private Collection<School> likeSchools;
private Collection<String>visitedSchool;
public void setName(String name) {
this.name = name;
}
 
public void setAge(int age) {
this.age = age;
}
public void setSchool(School school) {
this.school = school;
}
public void setLikeSchool(Collection<School> likeSchools) {
this.likeSchools = likeSchools;
}
public void setVisitedSchool(Collection<String> visitedSchool) {
this.visitedSchool = visitedSchool;
}
 
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("name:" + name + " age:" + age + " school:" + school.toString());
sb.append(" 向往的学校:");
for(School school : likeSchools){
sb.append(" " + school.getName());
}//for
sb.append(" 去过的学校:");
for(String schoolName : visitedSchool){
sb.append(" " + schoolName);
}//for
return sb.toString();
}
}

School类:

package com.sjf.bean;
 
 
public class School {
public String name;
public String location;
public void setName(String name) {
this.name = name;
}
public void setLocation(String location) {
this.location = location;
}
public String getName() {
return name;
}
public String getLocation() {
return location;
}
@Override
public String toString() {
return "name:" + name + " location:" + location;
}
}

5.1 访问集合成员([])

我们能做的最简单的事情就是从集合中提取一个成员,并将它装配到某个属性中:

<property name="school" value="#{schools[2]}"/>
我们从集合中挑选出第3 个学校(注意,集合的下标是从0 开始的),然后将它装配到school 属性中。中括号([])运算符会始终通过索引访问集合中的成员

5.2 查询集合成员(.?[] , .^[]  和 .$[])

如果我们想从学校集合中查询位于西安的学校,一种实现方式是将所有的schools Bean 都装配到Bean 的属性中,然后在该Bean 中增加过滤不符合条件的学校的逻辑。但是在SpEL 中,只需使用一个查询运算符(.?[])就可以简单做到,如下所示:

<property name="likeSchool" value="#{schools.?[location == '西安']}"/>
注:likeSchool属性是Collection<School>类型

查询运算符会创建一个新的集合,新的集合中只存放符合中括号内的表达式的成员。在这种场景下,likeSchool 属性被注入了位于西安的学校集合(西安电子科技大学,西安交通大学,西北工业大学)。

SpEL 同样提供两种其他查询运算符:.^[]  和 .$[] ,从集合中查询出第一个匹配项和最后一个匹配项

<property name="school" value="#{schools.^[location == '山东']}"/>
在这种场景下,school 属性被注入了符合条件的第一个匹配项:山东大学

<property name="school" value="#{schools.$[location == '山东']}"/>
在这种场景下,school 属性被注入了符合条件的最后一个匹配项:山东科技大学

5.3 投影集合(.![]

集合投影是从集合的每一个成员中选择特定的属性放入一个新的集合中。SpEL的投影运算符(.![])完全可以做到这点。

例如,假设我们仅仅需要包含学校名称的一个String 类型的集合,而不是School 对象的集合:

<property name="visitedSchool" value="#{schools.![name]}"/>
这个表达式的结果是visitedSchool属性将被赋予一个String 类型的集合,包含西安电子科技大学,山东大学,北京大学诸如此类的值。在中括号内的name属性决定了结果集合中要包含什么样的成员。这里只选择schools集合中每一个成员的name属性,即学校名称。


实例:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:list id = "schools">
<bean class="com.sjf.bean.School">
<property name="name" value="西安电子科技大学"/>
<property name="location" value="西安"></property>
</bean>
<bean class="com.sjf.bean.School">
<property name="name" value="西安交通大学"/>
<property name="location" value="西安"></property>
</bean>
<bean class="com.sjf.bean.School">
<property name="name" value="西北工业大学"/>
<property name="location" value="西安"></property>
</bean>
<bean class="com.sjf.bean.School">
<property name="name" value="山东大学"/>
<property name="location" value="山东"></property>
</bean>
<bean class="com.sjf.bean.School">
<property name="name" value="山东科技大学"/>
<property name="location" value="山东"></property>
</bean>
<bean class="com.sjf.bean.School">
<property name="name" value="北京大学"/>
<property name="location" value="北京"></property>
</bean>
<bean class="com.sjf.bean.School">
<property name="name" value="上海交通大学"/>
<property name="location" value="上海"></property>
</bean>
</util:list>
<bean id = "yoonaStudent" class = "com.sjf.bean.Student">
<property name="name" value="yoona"/>
<property name="age" value="24"/>
<property name="school" value="#{schools.^[location == '西安']}"/>
<property name="likeSchool" value="#{schools.?[location == '北京']}"/>
<property name="visitedSchool" value="#{schools.![name]}"/>
</bean>
</beans>

运行结果:

name:yoona age:24 school:name:西安电子科技大学 location:西安 向往的学校: 北京大学 去过的学校: 西安电子科技大学 西安交通大学 西北工业大学 山东大学 山东科技大学 北京大学 上海交通大学


来源于:《Spring实战》



网友评论

登录后评论
0/500
评论
sjf0115
+ 关注