Spring表达式语言
初次遇见@Value注解的时候,总以为它的功能非常生硬,只能注入硬编码的字面量值。后来随着学习的深入,渐渐发现@Value注解也能注入属性文件的值,非常好用。让人格外惊喜的是,@Value注解并未止步于此,还提供了通过Spring表达式语言(Spring Expression Language, SpEL)注入更加丰富的值的支持。比如,com.dream包定义了这样的类:
public class MusicWorld {
public static final String DREAM = "Dream";
}
则可这样把SpEL表达式的运算结果注入Bean中:
@Component
public class Music {
private String musicName = null;
@Value("#{T(com.dream.MusicWorld).DREAM + ' work'}")
public void setMusicName(String musicName) {
this.musicName = musicName;
}
// 省略getter方法
}
可以看到musicName属性带有@Value注解,其值是括在 #{} 里的SpEL表达式 T(com.dream.MusicWorld).DREAM + ' work' 。这样,Spring容器瞧见@Value注解之后就能解析SpEL表达式,把静态常量MusicWord.DREAM_MUSIC与字符串 work 连接之后得到字符串 Dream work 注入musicName属性里了。
非常明显,Spring表达式语言能够使用表达式描述值的注入,告诉Spring容器在运行时解析表达式之后把解析出来的结果注入Bean中。SpEL表达式必须括在 #{} 里,既可调用类的静态成员,Bean的实例成员;也可使用各种运算符进行值的运算。功能非常强大,语法非常简洁,用法非常方便,大大丰富了值的注入。现在,让我们一起瞧瞧SpEL表达式支持的功能。
SpEL表达式与字面量值
这是最简单的一种SpEL表达式。只要把硬编码的数字,布尔值,字符串等字面量值括在 #{} 里,就能通过SpEL表达式注入字面量值了。其中,字符串需要括在两个单引号 ' 里。如果需要注入的字符串本身就有单引号,可用两个单引号 '' 表示。以下示例就注入了字符串 One's dream :
@Component
public class Music {
private String musicName = null;
@Value("#{'One''s dream'}")
public void setMusicName(String musicName) {
this.musicName = musicName;
}
// 省略getter方法
}
SpEL表达式与集合
SpEL表达式支持集合的注入。如果集合的类型是Array,List或Set,可把集合的值使用逗号隔开之后括在 {} 里。如下所示:
@Component
public class Music {
private String[] musicNameArray = null;
private List<String> musicNameList = null;
private Set<String> musicNameSet = null;
@Value("#{{'One', 'Two', 'Three'}}")
public void setMusicNameArray(String[] musicNameArray) {
this.musicNameArray = musicNameArray;
}
@Value("#{{'One', 'Two', 'Three'}}")
public void setMusicNameList(List<String> musicNameList) {
this.musicNameList = musicNameList;
}
@Value("#{{'One', 'Two', 'Three'}}")
public void setMusicNameSet(Set<String> musicNameSet) {
this.musicNameSet = musicNameSet;
}
// 省略getter方法
}
如果集合的类型是Map,能以 键: 值 表示一对键值,而后把多对键值使用逗号隔开之后括在 {} 里。如下所示:
@Component
public class Music {
private Map<String, String> musicNameMap = null;
@Value("#{{OneKey: 'One', TwoKey: 'Two', ThreeKey: 'Three'}}")
public void setMusicNameMap(Map<String, String> musicNameMap) {
this.musicNameMap = musicNameMap;
}
// 省略getter方法
}
访问Bean的属性
SpEL表达式支持以 Bean的ID.Bean的属性名 这样的方式访问Spring容器里的Bean的属性。假如com.dream包定义了这样的组件:
@Component("music")
public class Music {
private String musicName = null;
@Value("Dream")
public void setMusicName(String musicName) {
this.musicName = musicName;
}
// 省略getter方法
}
则可这样访问ID为music的Bean的musicName属性,把它的值注入player中:
@Component("player")
public class Player {
private String playingMusicName = null;
@Value("#{music.musicName}")
public void setPlayingMusicName(String playingMusicName) {
this.playingMusicName = playingMusicName;
}
// 省略getter方法
}
访问Array,List,Set的值
SpEL表达式支持通过中括号 [] 括起索引访问Array,List,Set的值。假如com.dream包定义了这样的组件:
@Component("music")
public class Music {
private String[] musicNameArray = null;
private List<String> musicNameList = null;
private Set<String> musicNameSet = null;
@Value("#{{'One', 'Two', 'Three'}}")
public void setMusicNameArray(String[] musicNameArray) {
this.musicNameArray = musicNameArray;
}
@Value("#{{'One', 'Two', 'Three'}}")
public void setMusicNameList(List<String> musicNameList) {
this.musicNameList = musicNameList;
}
@Value("#{{'One', 'Two', 'Three'}}")
public void setMusicNameSet(Set<String> musicNameSet) {
this.musicNameSet = musicNameSet;
}
// 省略getter方法
}
则可这样指定索引取出Array,List,Set的值,把它们注入player中:
@Component("player")
public class Player {
private String playingMusicName_1 = null;
private String playingMusicName_2 = null;
private String playingMusicName_3 = null;
@Value("#{music.musicNameArray[0]}")
public void setPlayingMusicName_1(String musicName) {
this.playingMusicName_1 = musicName;
}
@Value("#{music.musicNameList[0]}")
public void setPlayingMusicName_2(String musicName) {
this.playingMusicName_2 = musicName;
}
@Value("#{music.musicNameSet[0]}")
public void setPlayingMusicName_3(String musicName) {
this.playingMusicName_3 = musicName;
}
// 省略getter方法
}
访问Map的值
SpEL表达式支持通过中刮号 [] 括起Map的键访问Map的值。假如com.dream包定义了这样的组件:
@Component("music")
public class Music {
private Map<String, String> musicNameMap = null;
@Value("#{{OneKey: 'One', TwoKey: 'Two', ThreeKey: 'Three'}}")
public void setMusicNameMap(Map<String, String> musicNameMap) {
this.musicNameMap = musicNameMap;
}
// 省略getter方法
}
则可这样指定Map的键取出Map的值,把它注入player中:
@Component("player")
public class Player {
private String playingMusicName = null;
@Value("#{music.musicNameMap['OneKey']}")
public void setPlayingMusicName(String playingMusicName) {
this.playingMusicName = playingMusicName;
}
// 省略getter方法
}
调用Bean的方法
SpEL表达式支持通过 Bean的ID.Bean的方法 这样的方式调用Spring容器里的Bean的方法。假如com.dream包定义了这样的组件:
@Component("music")
public class Music {
private String musicName = null;
@Value("Dream")
public void setMusicName(String musicName) {
this.musicName = musicName;
}
public String appendMusicName(String appendValue) {
return this.musicName + appendValue;
}
// 省略getter方法
}
则可这样调用ID为music的Bean的appendMusicName方法,把它的返回值注入player中:
@Component("player")
public class Player {
private String playingMusicName = null;
@Value("#{music.appendMusicName(' work')}")
public void setPlayingMusicName(String playingMusicName) {
this.playingMusicName = playingMusicName;
}
// 省略getter方法
}
T()运算符
SpEL表达式提供了T()运算符用于获取类的Class对象。只要把全限定类名指给T()运算符,T()运算符即能获取指定类的Class对象。因此,假如com.dream包定义了这样的类:
public class MusicWorld {
public static final String DREAM = "Dream";
}
则可这样通过SpEL表达式获取MusicWorld的Class对象,把它注入music中:
@Component("music")
public class Music {
private Class musicWorldClass = null;
@Value("#{T(com.dream.MusicWorld)}")
public void setMusicWorldClass(Class musicWorldClass) {
this.musicWorldClass = musicWorldClass;
}
// 省略getter方法
}
一种常用的用法是使用T()运算符获取类的Class对象之后访问类的静态成员。如下所示:
@Component("music")
public class Music {
private String musicName = null;
@Value("#{T(com.dream.MusicWorld).DREAM}")
public void setMusicName(String musicName) {
this.musicName = musicName;
}
// 省略getter方法
}
matches运算符
SpEL表达式支持通过matches运算符,以 字符串 matches 正则表达式 这样的方式判断某个字符串是否匹配某个正则表达式:如果匹配,则返回TRUE;否则返回FALSE。因此,假如com.dream包定义了这样的组件:
@Component("music")
public class Music {
private String musicName = null;
@Value("Dream")
public void setMusicName(String musicName) {
this.musicName = musicName;
}
// 省略getter方法
}
则可这样在SpEL表达式里使用matches运算符,看看music的musicName属性的值是不是只有字母,并把运算结果注入player中:
@Component("player")
public class Player {
private boolean isMusicValid = false;
@Value("#{music.musicName matches '^[a-zA-Z]+$'}")
public void setIsMusicValid(boolean isMusicValid) {
this.isMusicValid = isMusicValid;
}
// 省略getter方法
}
new运算符
SpEL表达式支持通过new运算符,以 new 带有全限定类名的构造函数 这样的方式创建类的实例。假如com.dream包定义了这样的类:
public class Music {
private String musicName = null;
public Music(String musicName) {
this.musicName = musicName;
}
}
则可这样在SpEL表达式里使用new运算符创建Music类的实例,把实例注入player中:
@Component("player")
public class Player {
private Music playingMusic = null;
@Value("#{new com.dream.Music('Dream')}")
public void setMusic(Music playingMusic) {
this.playingMusic = playingMusic;
}
// 省略getter方法
}
关系运行符
SpEL表达式提供了这些用于进行关系运算的关系运算符:
1.小于:< (也可使用文本lt)
2.大于:> (也可使用文本gt)
3.小于等于:<= (也可使用文本le)
4.大于等于:>= (也可使用文本ge)
5.等于:== (也可使用文本eq)
6.不等于:!= (也可使用文本ne)
这些关系运算符的用法与Java的关系运算符是一样,大家一瞧便知,不再详叙。
逻辑运算符
SpEL表达式提供了这些用于进行逻辑运算的逻辑运算符:
1.与:and
2.或:or
3.非:not
这些逻辑运算符的用法与Java的逻辑运算符是一样,大家一瞧便知,不再详叙。
算数运算符
SpEL表达式提供了这些用于进行算数运算的算数运算符:
1.数字的相加或字符串的相连:+
2.减:-
3.乘:*
4.除:/
5.取模:%
6.乘方:^
这些算术运算符的用法与Java的算术运算符是一样,大家一瞧便知,不再详叙。
?:运算符
SpEL表达式提供的?:运算符具有两种用法:一种是作为三元运算符(Ternary Operator)用于If-Then-Else的条件运算;一种是作为Elvis运算符用于简化三元运算符关于null值的用法。
?:运算符用作三元运算符时,它的用法和Java的?:运算符是一样的。如下所示:
#{music.musicName != null ? music.musicName: 'There is not any music.'}
这条SpEL表达式会先检查 music.musicName 的值是否不等于null:如果不等于null,则返回 music.musicName 的值;否则返回字符串 There is not any music.
另外,这条SpEL表达式还能使用Elvis运算符这样简化:
#{music.musicName ?: 'There is not any music.'}
Elvis运算符会先检查 music.musicName 的值是否不等于null:如果不等于null,则直接返回自己,也就是返回
music.musicName 的值;否则返回 ?: 后面的值,也就是返回字符串 There is not any music.
?.运算符
SpEL表达式提供了?.运算符用于安全地访问Bean的属性,调用Bean的方法。按照以往的写法,我们总是这样访问Bean的属性,调用Bean的方法:
#{music.musicName}
#{music.appendMusicName(' work')}
毫无疑问,如果music等于null,程序定会抛出异常。为了避免这样的错误,我们可用?.运算符修改如下:
#{music?.musicName}
#{music?.appendMusicName(' work')}
我们把 . 换成了?.运算符。这样,当music等于null时,这条SpEL表达式只会返回null,不会抛出异常;当music不等于null时,这条SpEL表达式才会访问Bean的属性,调用Bean的方法。
至此,关于SpEL表达式的介绍该告一段落了。当然,XML配置文件也是支持SpEL表达式的,而且用法与@Value注解一模一样。大家一看下面的示例就知道了:
<bean id="music_1" class="com.dream.Music">
<property name="musicName"
value="#{T(com.dream.MusicWorld).DREAM + ' work'}" />
</bean>
<bean id="music_2" class="com.dream.Music">
<constructor-arg type="java.lang.String"
value="#{T(com.dream.MusicWorld).DREAM + ' work'}" />
</bean>