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 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的屬性
以上的代碼等價於
還可以將獲取到的name值轉換為大寫
- <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()}/>
- <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;
- }
- }
- 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'}"/>
- <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;
- }
- <util:list id="cities">
- <bean class="cn.lovepi.***.SpelCity">
- <p:namep:name="Chicago" p:state="IL" p:population="2853114">
- <bean class="cn.lovepi.***.SpelCity">
- <p:namep: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表達式,只需了解知道即可。