Spring表達式語言(SpEL)


Spring也有自己的EL,叫Spring Expression Language,簡稱SpEl。其可以在程序中單獨使用,也可以在Spring應用中進行bean定義時使用。其核心是org.springframework.expression.Expression接口,Spring使用該接口來表示EL中的表達式。通過Expression接口的系列getValue()方法我們可以獲取對應Expression在特定EvaluationContext下的值,也可以通過其系列setValue()方法來設值。對應的Expression通常不是由我們直接來new對應實現類的實例,而是通過Spring提供的org.springframework.expression.ExpressionParser接口的系列parseExpression()方法來將一個字符串類型的表達式解析為一個Expression。以下是一個簡單的示例,在該示例中我們將字符串表達式“1+2”解析為一個Expression,然后進行計算得出其值為3。

String expressionStr = "1+2";
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(expressionStr);
Integer val = expression.getValue(Integer.class);
System.out.println(expressionStr + "的結果是:" + val);

最終輸出:1+2的結果是:3

Expression接口有一系列的getValue()方法,當其不接收任何參數時表示將會把Expression的計算結果當做一個Object進行返回,如果我們希望返回的是特定的類型,則可以傳遞對應的類型作為getValue()方法的參數,如上述示例中傳遞的Interger.class。我們也可以通過給Expression的getValue()方法傳遞EvaluationContext用以獲取在特定環境下的計算結果,也可以傳遞一個Object作為Expression計算的rootObject。

SpEL支持的表達式語法有:

  • 文本表達式
  • 對象屬性表達式
  • 數組、List 和 Map 表達式
  • 方法表達式
  • 操作符表達式
  • 安全導航操作符
  • 三元操作符
  • Elvis 操作符
  • 賦值表達式
  • 類型操作符
  • 創建對象操作符
  • 變量表達式
  • 集合選擇表達式
  • 集合元素布爾判斷

 一、文本表達式

文本表達式支持字符串、 數字(正數 、 實數及十六進制數) 、 布爾類型及 null。其中的字符表達式可使用單引號來表示,形如:'Deniro'。如果表達式中包含單引號或者雙引號字符,那么可以使用轉義字符 \。

數字支持負數 、小數、科學記數法、八進制數和十六進制數 。 默認情況下,實數使用 Double.parseDouble() 進行表達式類型轉換 。

ExpressionParser parser = new SpelExpressionParser();

//字符串解析, 寫成“Hello World”會報錯。
String str = (String) parser.parseExpression("'Hello World'").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);

二、對象屬性表達式

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

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

public class Account {

    private String name;
    private int footballCount;

    private Friend friend;

    private List<Friend> friends;

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

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    public int getFootballCount() {
        return footballCount;
    }

    public void setFriends(List<Friend> friends) {
        this.friends = friends;
    }

    public List<Friend> getFriends() {
        return friends;
    }

    public void setFriend(Friend friend) {
        this.friend = friend;
    }

    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);

總結:

  • SpEL 解析器適應力強,屬性名首字母大小寫均可。
  • 解析對象表達式時,需要傳入 EvaluationContext 上下文參數。

三、數組、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']

四、方法表達式

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);

注意:調用對象的私有方法會拋出異常。

五、操作符表達式

關系操作符

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);

instanceof 操作符后面是類型表達式,格式為 T(Java 包裝器類型),如整型 T(Integer)。注意:不能使用原生類型,如果這樣 T(int) 會返回錯誤的判斷結果。

matches 用於定義正則表達式,之后跟着單引號包裹着的正則表達式。

邏輯操作符

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

邏輯操作符 說明
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);

注意:邏輯操作符前后運算結果必須是布爾類型,否則會拋出 SpelEvaluationException。

運算操作符

SpEL 支持 Java 運算操作符,並遵守運算符優先級規則:

運算操作符 說明 支持的操作數類型
+ 加法 數字、字符串或日期
- 減法 數字或日期
* 乘法 數字
/ 除法 數字
% 取模 數字
^ 指數冪 數字
//解析器
ExpressionParser parser = new SpelExpressionParser();

//加法運算
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);

六、安全導航操作符

安全導航操作符來源於 Groovy 語言,使用它能夠避免空指針異常。一般在訪問對象時,需要驗證該對象是否為空,使用安全導航操作符就能避免繁瑣的空對象驗證方法。它的格式是在獲取對象屬性操作符“ .” 之前加一個 “ ?”。
final Account account = new Account("Deniro");
account.setFriend(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);

這里會先判斷 friend 對象是否為空;如果為空,則返回 "null" 字符串;否則返回需要的屬性值。

七、三元運算符

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);

八、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);

九、賦值表達式

可以通過賦值表達式來設置屬性的值,效果等同於調用 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);

十、類型操作符

類型操作符 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(com.codedot.spel.Account)").getValue(Class.class);
System.out.println(accountClass == Account.class);

//調用類靜態方法
double result = (double) parser.parseExpression("T(Math).abs(-2.5)").getValue();
System.out.println("result:" + result);

我們還可以直接通過 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);
    }
  • 嘗試直接加載類。
  • 如果找不到,則嘗試從已注冊的包前綴(java.lang)下加載類。所以如果需要加載的類在 java.lang 下,那么可以直接寫類名。
  • 如果都找不到,則拋出 SpelEvaluationException 異常。

十一、創建對象操作符

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

ExpressionParser parser = new SpelExpressionParser();

Account account = parser.parseExpression("new com.codedot.spel.Account" + "('Deniro')").getValue(Account.class);
System.out.println("name:"+account.getName());

十二、變量表達式

可以通過 #變量名 來引用在 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);

十三、集合選擇表達式

可以使用選擇表達式來過濾集合,從而生成一個新的符合選擇條件的集合 。它的語法是 ?[selectionExpression]。選擇符合條件的結果集的第一個元素的語法為 ^ [selectionExpression] ,選擇最后一個元素的語法為 $[selectionExpression]。選擇表達式也可應用於 Map 。
List<Integer> list = new ArrayList();
list.add(10);
list.add(21);
list.add(8);
list.add(33);

ExpressionParser parser = new SpelExpressionParser();
//過濾 list 集合中的元素
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 和 Set ,是針對集合中的每一個元素進行比較的;而對於 Map,則可以指定是元素的鍵(key)還是元素的值進行比較的。

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
//過濾 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);

十四、集合元素布爾判斷

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

ExpressionParser parser = new SpelExpressionParser();
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);

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM