說說 Spring 表示式語言(SpEL)中的各種表示式型別

deniro發表於2019-03-03

1 文字表示式

文字表示式支援字串、 日期 、 數字(正數 、 實數及十六進位制數) 、 布林型別及 null。其中的字元表示式可使用單引號來表示,形如:`Deniro`。如果表示式中包含單引號或者雙引號字元,那麼可以使用轉義字元 /

ExpressionParser parser
		= new SpelExpressionParser();

//字串解析
String str = (String) parser.parseExpression("`你好`").getValue();
System.out.println(str);

//整型解析
int intVal = (Integer) parser.parseExpression("0x2F").getValue();
System.out.println(intVal);

//雙精度浮點型解析
double doubleVal = (Double) parser.parseExpression("4329759E+22").getValue();
System.out.println(doubleVal);


//布林型解析
boolean booleanVal = (boolean) parser.parseExpression("true").getValue();
System.out.println(booleanVal);
複製程式碼

輸出結果:

你好
47
4.329759E28
true

數字支援負數 、小數、科學記數法、八進位制數和十六進位制數 。 預設情況下,實數使用 Double.parseDouble() 進行表示式型別轉換 。

2 物件屬性表示式

在 SpEL 中,我們可以使用物件屬性路徑(形如類名.屬性名.屬性名)來訪問物件屬性的值。

假設有一個賬號類,Account.java:

public class Account {


    private String name;
    private int footballCount;
    private Friend friend;

    public Account(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setFootballCount(int footballCount) {
        this.footballCount = footballCount;
    }

    public void addFriend(Friend friend) {

        this.friend = friend;
    }

    public int getFootballCount() {
        return footballCount;
    }

    public Friend getFriend() {
        return friend;
    }
}
複製程式碼

它包含姓名 name、足球數 footballCount 和一個朋友 friend 屬性。friend 屬性是一個 Friend 類:

public class Friend {
    private String name;

    public Friend(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
複製程式碼

解析物件屬性表示式:

//初始化物件
Account account=new Account("Deniro");
account.setFootballCount(10);
account.addFriend(new Friend("Jack"));

//解析器
ExpressionParser parser
		= new SpelExpressionParser();
//解析上下文
EvaluationContext context=new StandardEvaluationContext(account);

//獲取不同型別的屬性
String name= (String) parser.parseExpression("Name").getValue(context);
System.out.println(name);
int count= (Integer) parser.parseExpression("footballCount+1").getValue(context);
System.out.println(count);

//獲取巢狀類中的屬性
String friend= (String) parser.parseExpression("friend.name").getValue(context);
System.out.println(friend);
複製程式碼

輸出結果:

Deniro
11
Jack

  1. SpEL 解析器適應力強,屬性名首字母大小寫均可。
  2. 解析物件表示式時,需要傳入 EvaluationContext 上下文引數。

3 陣列、List 和 Map 表示式

陣列表示式支援 Java 建立陣列的語法,形如 new int[]{3,4,5},陣列項之間以逗號作為分隔符。**注意:**目前還不支援多維陣列。Map 表示式以鍵值對的方式來定義,形如 {name:`deniro`,footballCount:10}

//解析器
ExpressionParser parser
		= new SpelExpressionParser();

//解析一維陣列
int[] oneArray = (int[]) parser.parseExpression("new int[]{3,4,5}").getValue();
System.out.println("一維陣列開始:");
for (int i : oneArray) {
	System.out.println(i);
}
System.out.println("一維陣列結束");

//這裡會丟擲 SpelParseException
//        int[][] twoArray = (int[][]) parser.parseExpression("new int[][]{3,4,5}{3,4,5}")
//                .getValue();

//解析 list
List list = (List) parser.parseExpression("{3,4,5}").getValue();
System.out.println("list:" + list);

//解析 Map
Map map = (Map) parser.parseExpression("{account:`deniro`,footballCount:10}")
		.getValue();
System.out.println("map:" + map);

//解析物件中的 list
final Account account = new Account("Deniro");
Friend friend1 = new Friend("Jack");
Friend friend2 = new Friend("Rose");
List<Friend> friends = new ArrayList<>();
friends.add(friend1);
friends.add(friend2);
account.setFriends(friends);
EvaluationContext context = new StandardEvaluationContext(account);
String friendName = (String) parser.parseExpression("friends[0].name")
		.getValue(context);
System.out.println("friendName:" + friendName);
複製程式碼

從陣列與 List 獲取值,可以在括號內指定索引來獲取,形如上例中的 friends[0]。Map 中可通過鍵名來獲取,形如 xxx[`xxx`]

輸出結果:

一維陣列開始:
3
4
5
一維陣列結束
list:[3, 4, 5]
map:{account=deniro, footballCount=10}
friendName:Jack

4 方法表示式

SpEL 支援呼叫有訪問許可權的方法,這些方法包括物件方法、靜態方法,而且支援可變方法引數。除此之外,還可以呼叫 String 型別中的所有可訪問方法,比如 String.contains(`xxx`)

//解析器
ExpressionParser parser
		= new SpelExpressionParser();

//呼叫 String 方法
boolean isEmpty = parser.parseExpression("`Hi,everybody`.contains(`Hi`)").getValue
		(Boolean
				.class);
System.out.println("isEmpty:" + isEmpty);

/**
 * 呼叫物件相關方法
 */
final Account account = new Account("Deniro");
EvaluationContext context = new StandardEvaluationContext(account);

//呼叫公開方法
parser.parseExpression("setFootballCount(11)").getValue(context, Boolean
		.class);
System.out.println("getFootballCount:" + account.getFootballCount());

//呼叫私有方法,丟擲 SpelEvaluationException: EL1004E: Method call: Method write() cannot be found on net.deniro.spring4.spel.Account type
//        parser.parseExpression("write()").getValue(context,Boolean
//                .class);



//呼叫靜態方法
parser.parseExpression("read()").getValue(context, Boolean
		.class);

//呼叫待可變引數的方法
parser.parseExpression("addFriendNames(`Jack`,`Rose`)").getValue(context, Boolean
		.class);
複製程式碼

**注意:**呼叫物件的私有方法會丟擲異常。

輸出結果:

isEmpty:true
getFootballCount:11
讀書
friendName:Jack
friendName:Rose

5 操作符表示式

5.1 關係操作符

SpEL 支援 Java 標準操作符:等於、不等於、小於、小等於、大於、大等於、正規表示式和 instanceof 操作符。

//解析器
ExpressionParser parser
		= new SpelExpressionParser();

//數值比較
boolean result=parser.parseExpression("2>1").getValue(Boolean.class);
System.out.println("2>1:"+result);

//字串比較
result=parser.parseExpression("`z`>`a`").getValue(Boolean.class);
System.out.println("`z`>`a`:"+result);

//instanceof 運算子
result=parser.parseExpression("`str` instanceof T(String)").getValue(Boolean.class);
System.out.println("`str` 是否為字串 :"+result);

result=parser.parseExpression("1 instanceof T(Integer)").getValue(Boolean.class);
System.out.println("1 是否為整型 :"+result);

//正規表示式
result=parser.parseExpression("22 matches `\d{2}`").getValue(Boolean.class);
System.out.println("22 是否為兩位數字 :"+result);
複製程式碼

輸出結果:

2>1:true
`z`>`a`:true
`str` 是否為字串 :true
1 是否為整型 :true
22 是否為兩位數字 :true

  1. instanceof 操作符後面是型別表示式,格式為 T(Java 包裝器型別),如整型 T(Integer)。**注意:**不能使用原生型別,如果這樣 T(int) 會返回錯誤的判斷結果。
  2. matches 用於定義正規表示式,之後跟著單引號包裹著的正規表示式。

5.2 邏輯操作符

邏輯操作符支援以下操作:

邏輯操作符 說明
and 或 && 與操作
or 或 || 或操作
! 非操作

注意: 在 SpEL 中,不僅支援 Java 標準的邏輯操作符,還支援 and 與 or 關鍵字。

//解析器
ExpressionParser parser
		= new SpelExpressionParser();

//與操作
boolean result=parser.parseExpression("true && true").getValue(Boolean.class);
System.out.println("與操作:"+result);

//或操作
result=parser.parseExpression("true || false").getValue(Boolean.class);
System.out.println("或操作:"+result);

parser.parseExpression("true or false").getValue(Boolean.class);
System.out.println("或操作(or 關鍵字):"+result);

//非操作
result=parser.parseExpression("!false").getValue(Boolean.class);
System.out.println("非操作:"+result);

//丟擲 SpelEvaluationException: EL1001E: Type conversion problem, cannot convert from java.lang.Integer to java.lang.Boolean
//parser.parseExpression("!0").getValue(Boolean.class);
複製程式碼

輸出結果:

與操作:true
或操作:true
或操作(or 關鍵字):true
非操作:true

**注意:**邏輯操作符前後運算結果必須是布林型別,否則會丟擲 SpelEvaluationException。

5.3 運算操作符

SpEL 支援 Java 運算操作符,並遵守運算子優先順序規則:

運算操作符 說明 支援的運算元型別
+ 加法 數字、字串或日期
減法 數字或日期
* 乘法 數字
/ 除法 數字
% 取模 數字
^ 指數冪 數字
//加法運算
Integer iResult = parser.parseExpression("2+3").getValue(Integer.class);
System.out.println("加法運算:" + iResult);

String sResult = parser.parseExpression("`Hi,`+`everybody`").getValue(String.class);
System.out.println("字串拼接運算:" + sResult);

//減法運算
iResult = parser.parseExpression("2-3").getValue(Integer.class);
System.out.println("減法運算:" + iResult);

//乘法運算
iResult = parser.parseExpression("2*3").getValue(Integer.class);
System.out.println("乘法運算:" + iResult);

//除法運算
iResult = parser.parseExpression("4/2").getValue(Integer.class);
System.out.println("除法運算:" + iResult);

Double dResult = parser.parseExpression("4/2.5").getValue(Double.class);
System.out.println("除法運算:" + dResult);

//求餘運算
iResult = parser.parseExpression("5%2").getValue(Integer.class);
System.out.println("求餘運算:" + iResult);
複製程式碼

輸出結果:

加法運算:5
字串拼接運算:Hi,everybody
減法運算:-1
乘法運算:6
除法運算:2
除法運算:1.6
求餘運算:1

6 安全導航操作符

安全導航操作符來源於 Groovy 語言,使用它能夠避免空指標異常。一般在訪問物件時,需要驗證該物件是否為空,使用安全導航操作符就能避免繁瑣的空物件驗證方法。它的格式是在獲取物件屬性操作符“.” 之前加一個 “?”。

final Account account = new Account("Deniro");
account.addFriend(new Friend("Jack"));

//解析器
ExpressionParser parser
		= new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext(account);

String friendName=parser.parseExpression("friend?.name").getValue(context,String
		.class);
System.out.println("friendName:"+friendName);

//設定為 null
account.setFriend(null);
friendName=parser.parseExpression("friend?.name").getValue(context,String
		.class);
//列印出 null
System.out.println("friendName:" + friendName);
複製程式碼

輸出結果:

friendName:Jack
friendName:null

這裡會先判斷 friend 物件是否為空;如果為空,則返回 “null” 字串;否則返回需要的屬性值。

7 三元操作符

SpEL 支援標準的 Java 三元操作符:<表示式 1>?<表示式 2>:<表示式 3>

ExpressionParser parser
		= new SpelExpressionParser();

boolean result=parser.parseExpression("(1+2) == 3?true:false").getValue(Boolean
		.class);
System.out.println("result:"+result);
}
複製程式碼

輸出結果:

result:true

8 Elvis 操作符

Elvis 操作符是在 Groovy 中使用的三元操作符簡化版。

在三元操作符中,我們一般需要寫兩次變數名,比如下面程式碼段中的 title:

String title="News";
String actualTitle=(title!=null)?title:"tip";
複製程式碼

使用 Elvis 操作符後,可以將上述程式碼段簡寫為:

title?:"tip"
複製程式碼

SpEL 支援的 Elvis 操作符格式是:<var>?:<value>,如果 var 變數為 null,那就取 value 值,否則就取自身的值。所以 Elvis 操作符很適合用於設定預設值。

示例:

final Account account = new Account("Deniro");

//解析器
ExpressionParser parser
		= new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext(account);

String friendName=parser.parseExpression("name?:`無名`").getValue(context,String
		.class);
System.out.println("friendName:"+friendName);

//設定名字為 null
account.setName(null);
friendName=parser.parseExpression("name?:`無名`").getValue(context,String
		.class);
System.out.println("friendName:" + friendName);
複製程式碼

輸出結果:

friendName:Deniro
friendName:無名

9 賦值表示式

可以通過賦值表示式來設定屬性的值,效果等同於呼叫 setValue() 方法。

final Account account = new Account("Deniro");

//解析器
ExpressionParser parser
		= new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext(account);

String name=parser.parseExpression("name=`Jack`").getValue(context,String
		.class);
System.out.println("name:"+name);
複製程式碼

10 型別操作符

型別操作符 T 可以從類路徑載入指定類名稱(全限定名)所對應的 Class 的例項,格式為:T(全限定類名),效果等同於 ClassLoader#loadClass()

ExpressionParser parser
		= new SpelExpressionParser();

//載入 java.lang.Integer
Class integerClass=parser.parseExpression("T(Integer)").getValue(Class
		.class);
System.out.println(integerClass==java.lang.Integer.class);

//載入 net.deniro.spring4.spel.Account
Class accountClass=parser.parseExpression("T(net.deniro.spring4.spel.Account)")
		.getValue(Class
				.class);
System.out.println(accountClass==net.deniro.spring4.spel.Account.class);

//呼叫類靜態方法
double result = (double) parser.parseExpression("T(Math).abs(-2.5)").getValue();
System.out.println("result:" + result);
複製程式碼

輸出結果:

true
true
result:2.5

我們還可以直接通過 T 操作符呼叫類的靜態方法,格式為 T(全限定類名).靜態方法名,比如上面例子中求某數的絕對值 T(Math).abs(-2.5)

SpEL 中會使用 StandardTypeLocator#findType() 方法來載入類。 findType 方法定義如下:

public Class<?> findType(String typeName) throws EvaluationException {
		String nameToLookup = typeName;
		try {
			return ClassUtils.forName(nameToLookup, this.classLoader);
		}
		catch (ClassNotFoundException ey) {
			// try any registered prefixes before giving up
		}
		for (String prefix : this.knownPackagePrefixes) {
			try {
				nameToLookup = prefix + `.` + typeName;
				return ClassUtils.forName(nameToLookup, this.classLoader);
			}
			catch (ClassNotFoundException ex) {
				// might be a different prefix
			}
		}
		throw new SpelEvaluationException(SpelMessage.TYPE_NOT_FOUND, typeName);
	}
複製程式碼
  1. 嘗試直接載入類。
  2. 如果找不到,則嘗試從已註冊的包字首(java.lang)下載入類。所以如果需要載入的類在 java.lang 下,那麼可以直接寫類名。
  3. 如果都找不到,則丟擲 SpelEvaluationException 異常。

11 建立物件操作符

可以使用 new 操作符來建立一個新物件 。 除了基本型別(如整型、布林型等)和字串之外,建立其它類需要指明全限定類名( 包括包路徑 ) 。

Account account=parser.parseExpression("new net.deniro.spring4.spel.Account" +
	"(`Deniro`)").getValue(Account.class);
System.out.println("name:"+account.getName());
複製程式碼

輸出結果:

name:Deniro

12 變數表示式

可以通過 #變數名 來引用在 EvaluationContext 中定義的變數。通過 EvaluationContext#setVariable(name, val) 即可定義新的變數;name 表示變數名,val 表示變數值。

如果變數是集合,比如 list,那麼可以通過 #scores.[#this] 來引用集合中的元素。

Account account = new Account("Deniro");

ExpressionParser parser
		= new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext(account);

//定義一個新變數,名為 newVal
context.setVariable("newVal", "Jack");

//獲取變數 newVal 的值,並賦值給 User 的 name 屬性
parser.parseExpression("name=#newVal").getValue(context);
System.out.println("getName:" + account.getName());

//this 操作符表示集合中的某個元素
List<Double> scores = new ArrayList<>();
scores.addAll(Arrays.asList(23.1, 82.3, 55.9));
context.setVariable("scores", scores);//在上下文中定義 scores 變數
List<Double> scoresGreat80 = (List<Double>) parser.parseExpression("#scores.?[#this>80]")
		.getValue(context);
System.out.println("scoresGreat80:" + scoresGreat80);
複製程式碼

輸出結果:

getName:Jack
scoresGreate80:[82.3]

13 集合選擇表示式

可以使用選擇表示式來過濾集合,從而生成一個新的符合選擇條件的集合 。它的語法是 ?[selectionExpression]。選擇符合條件的結果集的第一個元素的語法為 ^ [selectionExpression] ,選擇最後一個元素的語法為 $[selectionExpression]。選擇表示式也可應用於 Map 。

//過濾 list 集合中的元素
final StandardEvaluationContext listContext = new
		StandardEvaluationContext(list);
List<Integer> great4List = (List<Integer>) parser.parseExpression("?[#this>4]")
		.getValue(listContext);
System.out.println("great4List:" + great4List);

//獲取匹配元素中的第一個值
Integer first = (Integer) parser.parseExpression("^[#this>2]")
		.getValue(listContext);
System.out.println("first:" + first);

//獲取匹配元素中的最後一個值
Integer end = (Integer) parser.parseExpression("$[#this>2]")
		.getValue(listContext);
System.out.println("end:" + end);
複製程式碼

輸出結果:

list:[3, 4, 5]
great4List:[5]
first:3
end:5

對於 List 和 Set ,是針對集合中的每一個元素進行比較的;而對於 Map,則可以指定是元素的鍵(key)還是元素的值進行比較的。

//過濾 Map
Map<String, Double> rank = new HashMap<>();
rank.put("Deniro", 96.5);
rank.put("Jack", 85.3);
rank.put("Lily", 91.1);
context.setVariable("Rank", rank);

//value 大於 90
Map<String,Double> rankGreat95= (Map<String, Double>) parser.parseExpression
		("#Rank.?[value>90]").getValue(context);
System.out.println("rankGreat95:" + rankGreat95);

//key 按字母順序,排在 L 後面
Map<String,Double> afterL= (Map<String, Double>) parser.parseExpression
		("#Rank.?[key>`L`]").getValue(context);
System.out.println("afterL:"+afterL);
複製程式碼

輸出結果:

rankGreat95:{Deniro=96.5, Lily=91.1}
nameOrder:{Lily=91.1}

14 集合元素布林判斷

通過表示式 ![projectionExpression],我們可以判斷集合中每一個元素是否符合表示式規則。

List list = (List) parser.parseExpression("{3,4,5}").getValue();
System.out.println("list:" + list);
   List<Boolean> isgreat4=(List<Boolean>)parser.parseExpression("![#this>3]")
		.getValue(list);
System.out.println("isgreat4:" + isgreat4);
複製程式碼

輸出結果:

isgreat4:[false, true, true]

也可以對 Map 物件進行類似判斷。

相關文章