到目前为止,我们为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 属性的界定符则:
<property
name
=
'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()运算符真正的价值在于,
通过该运算符可以访问指定类的静态方法和常量。
假设需要把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实战》