参考文档:

SpEL 官方文档
中文译文
SpEL表达式

1. 介绍

我们都知道属性占位符 ${...},而 SpEL 表达式则要放到 #{...}

spel 的简单用法

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'"); (1)
String message = (String) exp.getValue();
// message == 'Hello World'

调用 concat 方法

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')"); (1)
String message = (String) exp.getValue();
// message == 'Hello World!'

调用 String 的 Bytes 属性,对应 getBytes() 方法

ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes"); (1)
byte[] bytes = (byte[]) exp.getValue();
ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length"); (1)
int length = (Integer) exp.getValue();

调用 string 的方法

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'hello world'.toUpperCase()");
String message = exp.getValue(String.class);
// message == 'HELLO WORLD'

调用 string 构造方法

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()"); (1)
String message = exp.getValue(String.class);

2. Bean 中的表达式

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
    <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

    <!-- other properties -->
</bean>

可以通过 bean name 来获取其值

systemProperties 和 systemEnvironment 是 bean, 类型为 Map<String, Object>

<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
    <property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>

    <!-- other properties -->
</bean>

<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
    <property name="initialShapeSeed" value="#{ taxCalculator.defaultLocale }"/>

    <!-- other properties -->
</bean>

3. 用途总结

3.1 表示字面量

#{3.1415926}    //浮点数
#{9.87E4}       //科学计数法表示98700
#{'Hello'}      //String 类型
#{false}        //Boolean 类型

3.2 引用 bean、属性和方法

SpEL 可以通过 bean 的 name 去引用这个 bean,我们可以使用 SpEL 将一个 bean 装配到另外一个 bean 中,此时 beanName 作为 SpEL 表达式

#{sgtPeppers}                   //使用这个bean
#{sgtPeppers.artist}            //引用bean中的属性
#{sgtPeppers.selectArtist()}    //引用bean中的方法
#{sgtPeppers.selectArtist().toUpperCase()}      //方法返回值的操作

当然为了防止这个方法的返回值为空,上面的这个方法可以这样改

#{sgtPeppers.selectArtist()?.toUpperCase()}     

发现表达式中间仅多了一个“?”号,它表示的意思是如果selectArtist()方法的返回值不是null的话就执行大写操作,否则就不执行大写操作。

3.3 在表达式中使用类型

如果要在SpEL中访问类作用域的方法和常量的话,要依赖T()这个关键的运算符。例如,为了在SpEL中表达Java的Math类,需要按照如下的方式使用T()运算符。其中T()运算符的结果将会是一个Class类。如果需要的话,我们甚至可以将其装配到一个Class类型的bean属性中。但是T()运算符的真正价值在于它能够访问静态方法和常量

T(java.lang.Math)   
T(java.lang.Math).PI        //引用PI的值
T(java.lang.Math).random()  //获取0-1的随机数

3.4 运算符

运算符类型 运算符
算术比较 +、-、*、/、%、^
比较运算 <、>、==、<=、>=、lt、gt、rq、le、ge
逻辑运算 and、or、not、|
条件运算 ?: (ternary)、?:(Elvis)
正则表达式 matches

#{2*T(java.lang.Math).PI * circle.radius}               //圆周长计算
#{T(java.lang.Math).PI * circle.radius^2}               //圆面积计算
#{disc.title + 'by' + disc.artist}                      // + 是连接符
#{counter.total == 100}  #{counter.total eq 100}        //判断是否一致,返回true和false
#{counter.total > 100 ? "Winner" : "Loser"}             //三元表达式 
#{disc.title ?: 'Rattle'}                   //Elvis,如果是null的话结果则为Rattle
#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9._-]+\\.com'}  //正则表达式

3.5 计算集合

除了上面的内容之类,SpEL还加入了一些关于集合的技巧。最简单的可能就是引用一个简单列表中的元素了

#{jukebox.songs[4].title}

这句话表示计算 songs 集合中的第五个元素(从0开始)的 title 属性,这个属性来源于 beanName 为 jukebox 的 bean。当然我们还可以丰富一下它的内容,比如说随机选一首歌:

#{jukebox.songs[T(java.lang.Math).random*jukebox.songs.size()].title}

"[]"运算符用来从集合或者数组中按照索引获取元素,实际上,它还可以从 String 中获取一个字符,例如

#{'This is a test'[3]}          //获取的字符就是 's'

SpEL 还提供了查询运算符 .?[],它会用来对集合进行过滤,得到集合的一个子集。比如我们现在想要从 jukebox 中 artist 属性为 Aerosmith 的所有歌曲

可以看到选择运算符在它的 [] 中接受了另外一个表达式。当 SpEL 表达式迭代歌曲列表的时候,会对歌曲列表中的每一个条目计算这个表达式。如果表达式的计算结果为 true 的时候,那么条目会放到新的集合中去。否则的话,它就不会放到新的集合中去。

除了 .?[]之外,还有两种查询运算符 .^[].$[],他们分别用来在集合中查询第一个匹配项和最后一个匹配项。

#{jukebox.songs.?[artist eq 'Aerosmith']}               //匹配全部
#{jukebox.songs.^[artist eq 'Aerosmith']}               //匹配第一个
#{jukebox.songs.$[artist eq 'Aerosmith']}               //匹配最后一个

最后,SpEL 还提供了投影运算符 .![],它会从集合的每个成员中选择特定的属性放到另外一个集合中。作为样例,假设我们不想要歌曲对象的集合,而是要获取所有歌曲的名称的集合。如下的表达式会替我们完成将 title 属性投影到一个新的 String 类型的集合中:

#{jukebox.songs.![title]}

这个运算符一样可以和其他的运算符一起使用。比如我们可以使用如下的表达式获取 Aerosmith 所有歌曲的名称列表:

#{jukebox.songs.?[aitist eq 'Aerosmith'].![title]}