Spring表達式語言(SpEL)

1 簡介

Spring表達式語言(簡稱SpEL)是強大的表達式語言,它支持在運行時查詢和操縱對象圖表。語法與統一EL類似可是提供了額外的功能,最引人注目的是方法調用和基本字符串模版功能。java

儘管有另外幾種Java表達式語言可用,例如OGNL、MVEL和JBoss EL,Spring表達式語言被建立用於提供給Spring社區一個單一的支持良好的表達式語言,它能夠用於Spring portfolio中的所用產品。它的語言特性被Spring portfolio中的項目的需求驅動,包括基於Eclipse的Spring Tool Suite中的代碼補齊支持的工具需求。也就是說,SpEL是基於技術不可知的API,容許在須要時集成其餘表達式語言實現。正則表達式

儘管SpEL做爲Spring portfolio中表達式求值的基礎,它不直接與Spring綁定而且能夠被獨立使用。爲了自持,本章中的許多例子使用SpEL,就像它是一種獨立的表達式語言。這須要建立一些啓動基礎設施類例如解析器。大多數Spring用戶不須要處理這種基礎設施,而只會編寫表達式字符串用於求值。這個典型應用的一個例子是集成SpEL建立基於XML或者註解的bean定義,這會在「建立bean定義的表達式支持」一節中展現。spring

本章覆蓋了表達式語言的特性、API和語法。在一些地方,Inventor和Inventor的Society類被用於表達式求值的目標對象。這些類的聲明和用來填充它們的數據在本章的最後被列出。express

2 特性概覽

表達式語言支持下面的功能:編程

  • 字面表達式;
  • Boolean和相關的運算符;
  • 正則表達式;
  • 類表達式;
  • 訪問屬性、數組、列表和映射;
  • 方法調用;
  • 關係運算符;
  • 賦值;
  • 調用構造函數;
  • Bean引用;
  • 構造數組;
  • 內聯列表;
  • 內聯映射;
  • 三元運算符;
  • 變量;
  • 用戶定義的函數;
  • 列表投影;
  • 列表選擇;
  • 模板表達式。

3 使用Spring的Expression接口對錶達式求值

本節介紹SpEL接口和表達式語言的簡單實用方法。完整的語言參考能夠在「語言參考」一節中找到。數組

下面的代碼介紹SpEL API如何對字面字符串表達式'Hello World'求值。緩存

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

消息變量的值是'Hello World'。安全

最可能使用的SpEL類和接口位於org.springframework.expression包和它的子包以及spel.support包中。app

ExpressionParser接口用於解析一個表達式字符串。在這個例子中,表達式字符串是包圍着單引號的一個字符串文字。Expression接口用於對預先定義好的表達式字符串求值。調用parser.parseExpression和exp.getValue可能會分別拋出ParseException和EvaluationException。框架

SpEL支持許多特性,例如調用方法、訪問屬性和調用構造函數。

做爲調用方法的一個例子,咱們在字符串字面值上調用concat方法。

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

消息的值如今是"Hello World!"。

做爲訪問JavaBean屬性的一個例子,字符串的bytes屬性能夠以下調用:

ExpressionParser parser = new SpelExpressionParser();

// 調用 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes");
byte[] bytes = (byte[]) exp.getValue();

SpEL也支持使用辨準的點號訪問和設置嵌入屬性,例如prop1.prop2.prop3。

公有屬性也能夠被訪問。

ExpressionParser parser = new SpelExpressionParser();

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

能夠調用String的構造函數替代使用字符串字面值。

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

注意範型方法public <T> T getValue(Class<T> desiredResultType)的用法。使用這個方法避免了轉換表達式的值到指望類型的須要。若是值不能被轉換爲T或者不能使用註冊的類型轉換器轉換,將拋出EvaluationException。

SpEL更廣泛的用法是提供表達式字符串用於對特定的對象實體(稱爲根對象)求值。這裏有兩種方法,要選擇哪一個取決於表達式求值的對象是否會隨每次調用而更改。在下面的例子中,咱們獲取Inventor類的對象的name屬性。

// 建立並設置一個calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// 構造函數的參數是name, birthday和nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");

EvaluationContext context = new StandardEvaluationContext(tesla);
String name = (String) exp.getValue(context);

在最後一行,字符串變量name的值被設置爲"Nikola Tesla"。StandardEvaluationContext類能夠制定哪一個對象的name屬性被求值。若是根對象不太可能改變,就使用這種機制,它能夠在上下文中簡單的設置一次。若是根對象可能不斷的改變,能夠在每次調用getValue時提供這個根對象,以下面的例子所示:

// 建立並設置一個calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// 構造函數的參數是name, birthday和nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name");
String name = (String) exp.getValue(tesla);

在這種狀況下Inventor tesla被直接提供給getValue方法而且表達式求值的基礎設置在內部建立和管理一個默認的求值上下文,不須要咱們提供。

StandardEvaluationContext的構建是相對昂貴的而且在重複使用的過程當中它會創建緩存狀態以便可以更快的執行後續表達式的求值。所以,最好在可能的狀況下緩存和重用他們,而不是爲每一個表達式求值構建一個新的。

在一些狀況下,可能須要使用配置過的求值上下文而且在每次調用getValue時仍然提供不一樣的根對象。getValue容許在同一個調用中指定二者。在這些狀況下,調用時被傳遞的根對象會覆蓋任何求值上下文指定的對象。(就是使用getValue(EvaluationContext context, Object rootObject)方法)

在獨立使用SpEL時,須要建立解析器,解析表達式而且提供求值上下文和根上下文對象。然而,更廣泛的使用場景是僅提供SpEL表達式字符串做爲配置文件的一部分,例如用於Spring bean或Spring Web流定義。在這種狀況下,求值上下文,根對象和任何預約義的變量被隱式建立,不須要用戶指定除表達式外的任何東西。

做爲最後一個例子,用上一個例子中的Inventor對象展現布爾運算符的使用。

Expression exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(context, Boolean.class); // 求值爲true

3.1 EvaluationContext接口

當對一個表達式求值時使用EvaluationContext接口來解析屬性、方法、字段和幫助執行類型轉換。開箱即用的實現StandardEvaluationContext,使用反射來操做對象,經過緩存java.lang.reflect.Method、java.lang.reflect.Field和java.lang.reflect.Constructor接口來提升效率。

StandardEvaluationContext時經過setRootObject()方法或者傳遞給構造函數來參數來指定根對象的地方。也能夠經過setVariable()和registerFunction()方法指定將會在表達式中使用的變量和函數。變量和函數的使用在語言參考的變量和函數一節討論。也能夠經過StandardEvaluationContext註冊自定義的ConstructorResolver、MethodResolver和PropertyAccessor來擴展SpEL求值表達式的行爲。參考這些類的javadoc來獲取詳細信息。

類型轉換

默認的,SpEL使用Spring核心中可用的轉換服務(org.springframework.core.convert.ConversionService)。這個轉換服務包含許多內建的用於常見類型的轉換器,可是也徹底可擴展的以便添加兩種類型間的自定義轉換。另外它有感知範型的關鍵能力。這意味着當在表達式中使用範型類型,SpEL會嘗試轉換以維護遇到的任何對象的類型正確性。

在實踐中這意味着什麼?假設使用setValue()給一個List屬性賦值。屬性的類型實際上時List<Boolean>。SpEL將會識別到這個列表的元素須要在被添加到其中前轉換爲布爾類型。一個簡單的例子:

class Simple {
    public List<Boolean> booleanList = new ArrayList<Boolean>();
}

Simple simple = new Simple();

simple.booleanList.add(true);

StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple);

// 此處false做爲字符串被傳入。 

//SpEL和轉換服務將會正確的識別到這裏須要一個Boolean類型而且轉換它。

parser.parseExpression("booleanList[0]").setValue(simpleContext, "false");

// b 將會爲false
Boolean b = simple.booleanList.get(0);

3.2 解析器配置

可使用一個解析器配置對象(org.springframework.expression.spel.SpelParserConfiguration)配置SpEL表達式的解析器。配置對象控制一些表達式組件的行爲呢。例如,若是在數組或集合中檢索而且指定索引的元素爲null,能夠自動建立元素。當使用的表達式由屬性引用的鏈構成,這是很是有用的。若是在數組或集合中檢索而且指定索引超出了數組或集合如今的尺寸,能夠自動增加尺寸來容納索引。

class Demo {
    public List<String> list;
}

// 打開:
// - null應用自動初始化
// - 集合自動擴容
SpelParserConfiguration config = new SpelParserConfiguration(true,true);

ExpressionParser parser = new SpelExpressionParser(config);

Expression expression = parser.parseExpression("list[3]");

Demo demo = new Demo();

Object o = expression.getValue(demo);

// demo.list如今是一個有四個元素的集合
// 每一個元素是一個空字符串

也能夠配置SpEL表達式編譯器的行爲。

3.3 SpEL 編譯

Spring Framework 4.1包含了基礎的表達式編譯器。表達式一般被視爲在求值過程當中提供了大量的動態靈活性,但不能提供最佳性能。對於臨時的表達式使用是良好的,可是當被像Spring Integration這樣的其餘組件使用,性能是很是重要的而且沒有動態的實際須要。

新的SpEL編譯器用於知足這種需求。編譯器將會在求值期間即時生成一個真正的Java類,它體現了表達式的行文並使用它來實現更快的表達式求值。因爲缺乏表達式中的類型信息,編譯器在執行編譯時使用在表達式的解釋性評估期間收集的信息。例如,它沒法從表達式中得到一個屬性引用的 類型,可是在第一次解釋性求值過程當中會發現它時什麼。固然,若是各類表達式元素的類型隨着時間的推移而變化,那麼基於這些信息的編譯可能在稍後致使麻煩。所以,編譯最適合於重複糗事時類型信息不會改變的表達式。

對於一個以下的基本表達式:

someArray[0].someProperty.someOtherProperty < 0.1

包含了數組的方法,一些屬性的引用和數值操做,性能的增益能夠很是明顯。在50000次迭代的微型基準運行實例中,使用解釋器須要花費75ms而使用編譯版本的表達式只須要3ms。

編譯器配置

編譯器默認不會被打開,可是有兩種方法打開它。使用上面介紹的解析器配置處理或者當在另一個組件中嵌入使用SpEL時經過系統屬性。這一節討論這兩種方法。

明白編譯器能夠有幾種運行模式很是重要,模式在一個枚舉中(org.springframework.expression.spel.SpelCompilerMode)。模式以下:

  • OFF 編譯器被關閉,這是默認的配置;
  • IMMEDIATE 在即時模式中表達式將盡快的被編譯。通常是在第一次解釋性求值以後。若是編譯過的表達式失敗(通常是因爲類型改變,如上所述),那麼表達式求值的調用會收到一個異常;
  • MIXED 在混合模式中,表達式在解釋和編譯模式之間靜默的切換。在幾回解析模式運行後會切換到編譯模式,而且若是編譯模式出現了錯誤(例如類型發生改變,如上所述)那麼表達式會自動切換回解釋模式。稍後,它可能生成另外一個編譯形式並切換到它。基本上,用戶進入即時模式的異常被內部處理。

IMMEDIATE模式存在是由於MIXED模式的反作用可能致使表達式的問題。若是一個編譯的表達式在部分紅功後失敗,完成的部分可能已經對系統狀態產生影響。若是發生這種狀況,調用者不但願它在解釋模式下靜默的從新運行,由於表達式的一部分可能會運行兩次。

在選定一個模式後,使用SpelParserConfiguration配置解析器:

SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
    this.getClass().getClassLoader());

SpelExpressionParser parser = new SpelExpressionParser(config);

Expression expr = parser.parseExpression("payload");

MyMessage message = new MyMessage();

Object payload = expr.getValue(message);

指定編輯器模式的同時也能夠指定一個類加載器(傳入null是容許的)。編譯的表達式會被一個子類加載器中定義,子類加載器由被提供的類加載器建立。保證指定的類加載器能夠查看全部包含在表達式求值處理中的類型很是重要。若是沒有指定,將使用默認的類加載器(通常是運行表達式求值線程的上下文類加載器)。

第二種配置編譯器的方法用於在一些其餘組件中嵌入使用SpEL而且可能沒法經過配置類進行配置的狀況。在這些狀況下使用一個系統變量。變量spring.expression.complier.mode能夠被設定爲SpelCompilerMode枚舉值中的一種(off,immediate或者mixed)。

編譯器的侷限

Spring Framework 4.1引入了基本編譯框架。然而,框架尚未支持編譯每一種表達式。最初的重點是可能在性能關鍵環境中使用的常見表達式。下面這些種類的表達式如今沒法被編譯:

  • 包含賦值的表達式;
  • 依賴轉換服務的表達式;
  • 使用自定義解析器或者存取器的表達式;
  • 使用selection或projection的表達式(???)。

更多類型的表達式在未來會被編譯。

4 bean定義的表達式支持

SpEL表達式能夠與XML或基於註解的配置元數據一塊兒使用來定義BeanDefinition。在這兩種狀況下,定義表達式的語法格式爲#{<表達式字符串>}。

4.1 基於XML的配置

屬性或者構造函數參數值可使用表達式設置以下:

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

    <!-- 其餘屬性 -->
</bean>

systemProperties變量被預約義,因此像下面這樣在表達式中使用。注意在此上下文中,不須要使用#符號做爲預約義變量的前綴。

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

    <!-- 其餘屬性 -->
</bean>

也可使用name引用其餘bean屬性,例如:

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

    <!-- 其餘屬性 -->
</bean>

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

    <!-- 其餘屬性 -->
</bean>

4.2 基於註解的配置

@value註解能夠註釋字段、方法和方法/構造函數參數來指定默認值。

這裏是給字段變量設置默認值的一個例子。

public static class FieldValueTestBean

    @Value("#{ systemProperties['user.region'] }")
    private String defaultLocale;

    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale() {
        return this.defaultLocale;
    }

}

等價的使用屬性setter方法的設置以下:

public static class PropertyValueTestBean

    private String defaultLocale;

    @Value("#{ systemProperties['user.region'] }")
    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

    public String getDefaultLocale() {
        return this.defaultLocale;
    }

}

自動裝配的方法和構造函數也可使用@Value註解。

public class SimpleMovieLister {

    private MovieFinder movieFinder;
    private String defaultLocale;

    @Autowired
    public void configure(MovieFinder movieFinder,
            @Value("#{ systemProperties['user.region'] }") String defaultLocale) {
        this.movieFinder = movieFinder;
        this.defaultLocale = defaultLocale;
    }

    // ...
}
public class MovieRecommender {

    private String defaultLocale;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
            @Value("#{systemProperties['user.country']}") String defaultLocale) {
        this.customerPreferenceDao = customerPreferenceDao;
        this.defaultLocale = defaultLocale;
    }

    // ...
}

5 語言參考

5.1 字面表達式

字面表達式支持的類型有字符串、數值(整數、實數、十六進制)、布爾型和null。字符串由單引號分隔。要將一個單引號自己放在字符串中,使用兩個單引號。

下面的列表展現字面表達式的簡單使用方法。一般,它們不會像這樣使用,而是做爲更復雜表達式的一部分,例如在邏輯比較運算分的一側使用文字。

ExpressionParser parser = new SpelExpressionParser();

// 求值爲"Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();

double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();

// 求值爲 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();

boolean trueValue = (Boolean) parser.parseExpression("true").getValue();

Object nullValue = parser.parseExpression("null").getValue();

數值支持負號、指數符號和十進制點號的使用。默認的,使用Double.parseDouble()解析實數。

5.2 Properties、Arrays、Lists、Maps和Indexers

使用屬性引用瀏覽是容易的:只需使用點好來指示嵌套的屬性值。Inventor類的實例pupin和tesla,使用「例子中使用的類」一節列出的數據填充。爲了「向下」瀏覽,獲取Tesla的出生年份和Pupin的出生城市,使用一下的表達式。

// 求值爲1856
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);

String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);

屬性名的首字母不區分大小寫。數組和列表的內容使用方括號符號獲取。

ExpressionParser parser = new SpelExpressionParser();

// Inventions數組
StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);

// 求值爲 "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
        teslaContext, String.class);

// Members列表
StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);

// 求值爲 "Nikola Tesla"
String name = parser.parseExpression("Members[0].Name").getValue(
        societyContext, String.class);

// 列表和數組 navigation
// 求值爲"Wireless communication"
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
        societyContext, String.class);

Map的內容經過在括號內指定文字的鍵值得到。在下面的例子中,由於Officers map的鍵是字符串,能夠指定字符串字面值。

// Officer's Dictionary

Inventor pupin = parser.parseExpression("Officers['president']").getValue(
        societyContext, Inventor.class);

//  求值爲 "Idvor"
String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
        societyContext, String.class);

// 設置值
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
        societyContext, "Croatia");

5.3 內聯列表

列表可使用{}符號直接在表達式中表示。

// 求值爲一個Java列表包含四個數值
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);

List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);

{}自身表示一個空列表。出於性能的緣由,若是列表自己徹底由固定文字組成,則會建立一個常量列表來表示表達式,而不是在每次求值時構建一個新列表。

5.4 內聯映射

映射也可使用{key:value}符號直接在表達式中表示。

// 求值爲一個包含兩個鍵值對的Java映射
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);

Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);

{:}自身表示一個空映射。出於性能的緣由,若是映射自己徹底由固定文字或其餘關聯的常熟結構(列表或映射)組成,則會建立一個常量列表來表示表達式,而不是在每次求值時構建一個新列表。引用映射鍵是可選的,上面的示例中沒有使用引用的鍵。

5.5 數組構建

可使用與Java類似的語法構建數組,能夠選擇提供初始化器以在構造的時間填充數組。

int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);

// 使用初始化器構造數組
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);

// 多維數組
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);

如今在構造多維數組時不容許使用初始化器。

5.6 方法

使用典型的Java編程語法調用方法。也能夠在字面值上調用方法,同時也支持變量。

// 字符串字面值, 求值爲"bc"
String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);

// 求值爲 true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean.class);

5.7 運算符

關係運算符

關係運算符:相等,不等,小於,小於等於,大於,大於等於支持使用標準運算符。

// 求值爲 true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);

// 求值爲 false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);

// 求值爲 true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);

大於/小於與null比較遵循一個簡單原則:null被認爲是沒有(即,不是0)。所以,任何其餘值老是大於null(X > null總爲true)而且沒有任何其餘值比null小(X < null總爲false)。若是更傾向於使用數值比較,請避免基於數字的null比較而使用與0的比較(例如 X > 0 或 X < 0)。

除了標準關係操運算符SpEL支持instanceof和機遇正則表達式的matches運算符。

// 求值爲false
boolean falseValue = parser.parseExpression(
        "'xyz' instanceof T(Integer)").getValue(Boolean.class);

// 求值爲true
boolean trueValue = parser.parseExpression(
        "'5.00' matches '\^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

//求值爲false
boolean falseValue = parser.parseExpression(
        "'5.0067' matches '\^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

當心使用基本類型,由於它們會當即被裝箱爲包裹類型,因此 1 instanceof T(int)求值爲false而 1 instanceof T(Integer)求值爲true。

每一個符號運算符也能夠被替換爲純粗的字母等價形式。這避免了使用的符號在嵌入表達式的文檔類型中具備特殊含義的問題(例如,XML文檔)。文本的等價形式以下:lt (<), gt (>), le (⇐), ge (>=), eq (==), ne (!=), div (/), mod (%), not (!)。它們是不區分大小寫的。

邏輯運算符

邏輯運算符中支持與、或和非。它們的用法展現以下。

/ -- 與 --

// 求值爲 false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);

// 求值爲 true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- 或 --

// 求值爲 true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);

// 求值爲 true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- 非 --

// 求值爲 false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);

// -- 與非 --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

數學運算符

加運算符能夠在數值和字符串使用。減法、乘法和除法只能在數值上使用。此外還支持求模(%)和指數冪(^)。執行標準運算符優先級。這些運算符展現以下:

// 加
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2

String testString = parser.parseExpression(
        "'test' + ' ' + 'string'").getValue(String.class); // 'test string'

// 減
int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4

double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000

// 乘
int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6

double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0

// 除
int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2

double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0

// 模
int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3

int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1

// 運算符優先級
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21

5.8 賦值

使用賦值運算符設置屬性。這一般經過調用setValue完成,但也能夠在調用的getValue內部完成。

Inventor inventor = new Inventor();
StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor);

parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2");

// alternatively

String aleks = parser.parseExpression(
        "Name = 'Alexandar Seovic'").getValue(inventorContext, String.class);

5.9 類型

特殊的T運算符可用於指定java.lang.Class(類型)的實例。也可使用此運算符調用靜態方法。StandardEvaluationContext使用TypeLocator查找類型,而且StandardTypeLocator(能夠被替換)構建了對java.lang包的理解。這意味着T()指向java.lang包中的類型不須要使用徹底限定符,可是全部其餘類型的引用必須使用。

Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);

Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);

boolean trueValue = parser.parseExpression(
        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
        .getValue(Boolean.class);

5.10 構造函數

使用new運算符能夠調用構造函數。除了基本類型和字符串(int、float等)意外的其餘類型必須使用徹底限定的類名。

Inventor einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor.class);

//在列表的add方法中建立新的inventor實例
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor(
            'Albert Einstein', 'German'))").getValue(societyContext);

5.11 變量

使用#variableName語法在表達式中引用變量。使用StandardEvaluationContext上的setVairable方法設置變量。

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
context.setVariable("newName", "Mike Tesla");

parser.parseExpression("Name = #newName").getValue(context);

System.out.println(tesla.getName()) // "Mike Tesla"

#this和#root變量

變量#this老是被定義並引用當前求值對象(針對哪一個非限定引用被解析)。變量#root老是被定義並引用根上下文對象。儘管#this可能由於被求值的表達式組件而發生變化,可是#root老是引用根對象。

// 建立整數數組
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));

// 建立解析器而且設置變量primes爲整型數組
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("primes",primes);

// list中的全部基本數值大於10 (u使用選擇器 ?{...})
// 求值爲 [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
        "#primes.?[#this>10]").getValue(context);

5.12 函數

能夠經過註冊能夠在表達式字符串中調用的用戶自定義函數擴展SpEL。使用StandardEvaluationContext的以下方法註冊函數:

public void registerFunction(String name, Method m)

一個Java方法的引用提供函數的實現。例如,一個工具方法反轉字符串的使用方法以下所示:

public abstract class StringUtils {

    public static String reverseString(String input) {
        StringBuilder backwards = new StringBuilder();
        for (int i = 0; i < input.length(); i++)
            backwards.append(input.charAt(input.length() - 1 - i));
        }
        return backwards.toString();
    }
}

而後使用求值上下文註冊這個方法而且能夠在表達式字符串中使用。

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

context.registerFunction("reverseString",
    StringUtils.class.getDeclaredMethod("reverseString", new Class[] { String.class }));

String helloWorldReversed = parser.parseExpression(
    "#reverseString('hello')").getValue(context, String.class);

5.13 Bean引用

若是求值上下文被配置了一個bean極細氣,能夠在表達式中使用@符號查找bean。

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

//在求值過程當中它會調用MyBeanResolver的resolve(context, "foo")方法
Object bean = parser.parseExpression("@foo").getValue(context);

爲了獲得工廠方法自己,bean名字須要添加&前綴而不是@。

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// 在求值過程當中它會調用MyBeanResolver的resolve(context, "foo")方法
Object bean = parser.parseExpression("&foo").getValue(context);

5.14 三元運算符(If-Then-Else)

能夠在表達式中使用三元運算符表示if-then-else條件邏輯。一個小例子以下:

String falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String.class);

在這種狀況下,布爾值false會致使返回字符串值「falseExp「。一個更實際的例子以下:

parser.parseExpression("Name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");

expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
        "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";

String queryResultString = parser.parseExpression(expression)
        .getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"

請查看下一節的Elvis運算符,它有更簡短的三元運算符語法。

5.15 Elvis運算符

Elvis運算符縮短了三元操做符的語法,它在Groovy語言中使用。在三元運算符語法中,一般須要重複一個變量兩次,例如:

String name = "Elvis Presley";
String displayName = name != null ? name : "Unknown";

做爲替代可使用Elvis運算符,由於與貓王的髮型類似而得名。

ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("name?:'Unknown'").getValue(String.class);

System.out.println(name); // 'Unknown'

下面是一個更復雜的例子。

ExpressionParser parser = new SpelExpressionParser();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);

String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);

System.out.println(name); // Nikola Tesla

tesla.setName(null);

name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);

System.out.println(name); // Elvis Presley

5.16 安全的導航運算符

安全的導航運算符來自Groovy語言,用於避免NullPointerException。通常狀況下,當你引用一個對象,你也許須要在訪問對象的方法或者屬性以前驗證它是否爲空。爲了不這個操做,安全導航運算符會簡單的返回null替代拋出異常。

ExpressionParser parser = new SpelExpressionParser();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));

StandardEvaluationContext context = new StandardEvaluationContext(tesla);

String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
System.out.println(city); // Smiljan

tesla.setPlaceOfBirth(null);

city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);

System.out.println(city); // null - 不會拋出NullPointerException!!!

Elvis運算符能夠用於在表達式中設置默認值,例如一個一個@Value表達式中:@Value("#{systemProperties['pop3.port'] ?: 25}")。這將注入系統屬性pop3.port(若是已定義)或25(若是不是)。

5.17 列表選擇器

選擇器是一個強大的表達式語言特性,它運行你經過選擇源列表中的元素將源列表轉換爲另外一個一個列表。

選擇器使用語法.?[選擇器表達式]。它將鍋略列表而且返回一個包含原始元素子集的新列表。例如,選擇器容許咱們簡單的獲取一個塞爾維亞發明家列表:

List<Inventor> list = (List<Inventor>) parser.parseExpression(
        "Members.?[Nationality == 'Serbian']").getValue(societyContext);

列表和映射均可以使用選擇器。在前一種狀況下,根據每一個單獨列表元素求值選擇標準;同時針對映射,根據每一個映射鍵值對(Java Map.Entry類型)求值選擇標準。

映射的鍵值對能夠做爲屬性在選擇器中訪問。

下面的表達式會返回一個新的映射由原始映射中值小於27的鍵值對組成。

Map newMap = parser.parseExpression("map.?[value<27]").getValue();

除了返回全部選定的元素,還能夠檢索第一個或最後一個值。要獲取與選擇器匹配的第一個鍵值對語法爲^[...];同時獲取匹配的選擇器的最後一條,語法爲$[...]。

5.18 列表投影(???)

投影容許集合驅動子表達式的求值,結果是一個新的集合。投影的語法是![projectionExpression]。最容易理解的例子,假設咱們有一個發明家的列表,但但願獲取他們出生的城市的列表。高效的,咱們對發明人列表中的每一個條目的"placeOfBirth.city"求值。使用投影:

// 返回 ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");

映射也能夠被用於驅動投影而且在這種狀況下會根據Map中的每一個鍵值對對投影表達式求值。映射投影的結果是由對每一個映射鍵值對的投影表達式的求值組成的列表。

5.19 表達式模板

表達式模板容許字面文本與一個或多個求值模塊混合在一塊兒。每一個求值模塊用你定義的前綴和後綴自負分隔,一個廣泛的選擇是使用#{ }做爲分隔符。例如:

String randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}",
        new TemplateParserContext()).getValue(String.class);

// 求值爲 "random number is 0.7038186818312008"

字符串經過鏈接字面文本"random number is"與#{}分隔符中的表達式的求值結果進行求值,在這個例子中是調用random()方法的結果。傳遞給parseExpression()方法的第二個參數是ParserContext類型。ParserContext接口用於影響表達式如何解析以支持表達式模板功能。TemplateParserContext的定義以下:

public class TemplateParserContext implements ParserContext {

    public String getExpressionPrefix() {
        return "#{";
    }

    public String getExpressionSuffix() {
        return "}";
    }

    public boolean isTemplate() {
        return true;
    }
}

6 例子中使用的類

Inventor.java

package org.spring.samples.spel.inventor;

import java.util.Date;
import java.util.GregorianCalendar;

public class Inventor {

    private String name;
    private String nationality;
    private String[] inventions;
    private Date birthdate;
    private PlaceOfBirth placeOfBirth;

    public Inventor(String name, String nationality) {
        GregorianCalendar c= new GregorianCalendar();
        this.name = name;
        this.nationality = nationality;
        this.birthdate = c.getTime();
    }

    public Inventor(String name, Date birthdate, String nationality) {
        this.name = name;
        this.nationality = nationality;
        this.birthdate = birthdate;
    }

    public Inventor() {
    }

    public String getName() {
        return name;
    }

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

    public String getNationality() {
        return nationality;
    }

    public void setNationality(String nationality) {
        this.nationality = nationality;
    }

    public Date getBirthdate() {
        return birthdate;
    }

    public void setBirthdate(Date birthdate) {
        this.birthdate = birthdate;
    }

    public PlaceOfBirth getPlaceOfBirth() {
        return placeOfBirth;
    }

    public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
        this.placeOfBirth = placeOfBirth;
    }

    public void setInventions(String[] inventions) {
        this.inventions = inventions;
    }

    public String[] getInventions() {
        return inventions;
    }
}

PlaceOfBirth.java

package org.spring.samples.spel.inventor;

public class PlaceOfBirth {

    private String city;
    private String country;

    public PlaceOfBirth(String city) {
        this.city=city;
    }

    public PlaceOfBirth(String city, String country) {
        this(city);
        this.country = country;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String s) {
        this.city = s;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

}

Society.java

package org.spring.samples.spel.inventor;

import java.util.*;

public class Society {

    private String name;

    public static String Advisors = "advisors";
    public static String President = "president";

    private List<Inventor> members = new ArrayList<Inventor>();
    private Map officers = new HashMap();

    public List getMembers() {
        return members;
    }

    public Map getOfficers() {
        return officers;
    }

    public String getName() {
        return name;
    }

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

    public boolean isMember(String name) {
        for (Inventor inventor : members) {
            if (inventor.getName().equals(name)) {
                return true;
            }
        }
        return false;
    }

}
相關文章
相關標籤/搜索