Spring 官方文檔翻譯(第十章)

10 Spring Expression Language (SpEL)

10.1 簡介

Spring Expression Language是一個強大的表達式語言,支持在運行時查詢和操作對象。這個語言的語法跟Unified EL類似,但是他提供額外的功能,特別是方法調用和基本的字符串模板功能。

雖然Java也有幾種Java表達式語言,像OGNL,MVEL和JBoss EL,而Spring的表達式語言爲SPring提供了一個很好的支持表達式語言,可以用在Spring下的所有產品中。其語言特性是受Spring產品中的項目要求所驅動的,包括基於Spring Tool Suite的eclipse中代碼完成支持的工具要求。也就是說,SpEL是基於不可知論API的技術,在需要的時候允許整合其他的表達式語言實現。

雖然Spring是作爲Spring組合中表達式評估的基礎,但是與Spring並沒有直接的聯繫,所以他是可以低濃度使用的。本章的很多例子都是將SpEL當做一個獨立的表達式語言看待,所以它需要一些引導的基礎類,比如解析器。大多數Spring用戶不需要了解這些基礎設施,主需要評估一下表達式字符串。這個典型用途是整合SpEL到創建基於bean定義的XML或註解中(在 Expression support for defining bean definitions中有體現)。

本章涵蓋表達式語言的特徵,API以及語言語法。在一些地方,Inventor和Inventor的Society類用於表達式評估的目標對象。這些類的聲明將在本章末尾列出。

10.2 功能概述

表達式語言支持以下功能:

  • 文字表達
  • 布爾和關係運算符
  • 正則表達式
  • 類表達式
  • 訪問屬性,數組,列表,map
  • 方法調用
  • 關係運算符
  • 分配(Assignment)
  • 調用構造函數
  • Bean引用
  • 數組構造
  • 內聯列表
  • 內聯map
  • Ternary operator
  • 變量
  • 用戶自定義功能
  • Collection projection
  • Collection selection
  • 模板表達式

10.3 Spring的表達式接口評估表達式

本小節介紹SpEL接口和表達式語言的簡單實用,完整的語言引用可以參考”“語言引用(Language Reference)”部分。

下面這些代碼介紹使用SpELl API評估文字表達式”“Hello World”:


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

message變量最後的值爲‘Hello World’。

經常使用的SpEL類和接口位於包org.springframework.expression中和它的子包spel.support

接口ExpressionParser用於解析表達式字符串的,在這個例子中,表達式字符串是被單括號括起來的文字表達式。而接口Expression則是用來評估之前定義的表達式字符串。當調用parser.parseExpressionexp.getValue這兩個方法時,需要分別拋出ParseException和EvaluationException異常。

SpEL支持很多功能,比如調用方法,訪問屬性和調用構造器。

一個方法調用的例子,我們調用文字字符串的concat方法:


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

message的值是“Hello World!”。

一個調用JavaBean屬性的例子,調用String的屬性Bytes:


ExpressionParser parser = new SpelExpressionParser();

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

SpEL也支持嵌套屬性,用標準的.符號,像prop1.prop2.prop3和屬性值的設置,也可以訪問公共字段。


ExpressionParser parser = new SpelExpressionParser();

// invokes '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屬性。


// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// The constructor arguments are name, birthday, and 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,像下面例子一樣:


// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// The constructor arguments are name, birthday, and 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);

在這個例子中tesla直接用到getValue中,表達式評估設備創建和管理一個默認的評估上下文-它不需要提供。

相對來說,創建StandardEvaluationContext的成本略高,因爲在重複利用中建立緩存狀態,能夠更快的執行下後續的表達式評估。基於這個原因,要儘可能的緩存和重利用,而不是爲每一個表達式創建一個新的評估。

在某些情況下可能需要使用配置的評估上下文,但在調用getValue時仍然需要提供一個不同的根對象。getValue在同一個調用中,可以允許兩個特例。在某些情況下,調用的根對象被認爲是覆蓋了評估上下文的任何(可能爲空)特例。

作爲最後一個例子,用上面的例子講一個顯示布爾類型的用法:


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

10.3.1 EvaluationContext接口

EvaluationContext接口用在評估需要解決屬性,方法,字段的表達式,來執行類型轉換的需求。StandardEvaluationContext使用反射來操作對象,緩存java.lang.reflect.Methodjava.lang.reflect.Fieldjava.lang.reflect.Constructor實例以提高性能。

StandardEvaluationContext是根據方法setRootObject()或把根對象放入構造器中來評估根對象。同樣也可以用方法setVariable()registerFunction()用在表達式中特殊的變量和功能。StandardEvaluationContext也是可以註冊自定義ConstructorResolverMethodResolverPropertyAccessor來擴展SpEL評估表達式的地方。

類型轉換

SpEL默認使用Spring core(org.springframework.core.convert.ConversionService)的轉換服務。這個轉換服務有很多內置的常用的轉換器,但也可以擴展添加類型之間自定義轉換器。此外,它具有泛型感知的強大功能,這意味着,當在表達式中含有泛型時,SpEL將會嘗試轉換以維持任何對象類型正確性。

在實際開發中怎麼用呢?假設使用setValue()被設置一個List屬性,這個屬性實際上是List。SpEL將會在被放置之前把list元素轉成Boolean。一個簡單例子:


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

Simple simple = new Simple();

simple.booleanList.add(true);

StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple);

// false is passed in here as a string. SpEL and the conversion service will
// correctly recognize that it needs to be a Boolean and convert it
parser.parseExpression("booleanList[0]").setValue(simpleContext, "false");

// b will be false
Boolean b = simple.booleanList.get(0);

10.3.2 解析配置

用解析配置對象(org.springframework.expression.spel.SpelParserConfiguration)可以配置SpEL表達式解析,配置對象控制表達式組件的行爲。例如,如果數組或集合的某個索引元素爲null,配置對象就可以自動創建一個元素。這對使用一組屬性引用的表達式很有用處。當索引數值超過數組或列表本身長度時,會自動的增加他們的長度。


class Demo {
    public List<String> list;
}

// Turn on:
// - auto null reference initialization
// - auto collection growing
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 will now be a real collection of 4 entries
// Each entry is a new empty String

當然還可以配置SpEL編譯器的行爲。

10.3.3 SPEL編譯

Spring Framework 4.1包含一個基本的表達式編譯器。表達式通常在評估階段提供很多動態靈活功能,而不是最優的性能。對於偶然的幾次使用表達式這是沒問題的,但是如果是其他組件(比如Spring Integration)使用的話,需要的是性能而不是動態靈活。

而新的SpEL編譯器打算處理這個需求。編譯器將在評估過程中生成一個Java類以體現表達式的行爲,這樣可以獲得更快的表達式評估。由於缺少表達式類,編譯器在執行編譯時會在評估階段就行信息收集。例如,剛開始時,編譯器不知道引用屬性的類型,但是通過第一次的解釋性評估,它就知道類型是什麼。當然,如果不同的表達式元素隨時間而變化的話,編譯器獲得的信息接下來也會有問題發生。所以編譯器適用於類型信息不會發生改變的表達式。

一個基本的表達式像這樣:


someArray[0].someProperty.someOtherProperty < 0.1

表達式設計數組訪問,屬性的引用和數值運算,能很明顯的看到性能的提升。5000此迭代的例子中,之前的評估需要75ms,而使用編譯器評估只需要3ms。

編譯器配置

編譯器不是默認開啓的,有兩種方式開啓。一種是在之前講的解析配置中打開,另一種是通過SpEL嵌入到其他組件的系統屬性打開。本小節會討論這兩種操作。

編譯器運行的模式都在枚舉org.springframework.expression.spel.SpelCompilerMode中。模式如下:

  • OFF - 默認關閉。
  • IMMEDIATE - 立即模式,表達式會被儘快執行。這個模式通常是在第一次解釋性評估之後。如果執行失敗,會收到異常報告。
  • MIXED - 混合模式中,表達式會在解釋模式和編譯模式之間切換。經過一些解釋運行後會轉成編譯模式;然後如果編譯格式報錯(像之前講的類型變換),就自動轉成解釋模式,然後循環往復。基本上IMMEDIATE模式的異常都是內部自己解決了。

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.compiler.mode屬性可以設置爲SpelCompilerMode枚舉值(off, immediate, 或mixed)的其中一個。

編譯器限制

雖然已經有了Spring Framework 4.1的基本編譯器框架,但是它並不支持表達式的每個類型。最初的關注點是在關鍵環境中常用的表達式。以下是不支持的表達式:

  • 涉及到分配的表達式;
  • 依賴轉換器服務的表達式;
  • 使用自定義解析器或表達器的表達式;
  • 使用selection or projection的表達式。

10.4 Expression support for defining bean definitions

SpEL表達式可以與用於定義BeanDefinitionXML配置元素的XML或註解兩種方法一起使用。在這兩種情況下,定義表達式的格式爲#{<expression string>}

10.4.1 基於XML配置

屬性值或構造器參數可以用表達式表示:


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

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

systemProperties變量被重新定義,所以你可以在自己的表達式中使用。不需要在重新定義的變量前加#


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

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

還可以通過屬性的名稱引用:


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

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

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

<!-- other properties -->

10.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;
    }

}

註解用到參數中的例子:


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

    // ...
}

10.5 Language Reference

10.5.1 文字表達式

文字表達式支持的類型有字符串,數值(int,real,hex),布爾和null。字符串類型用單引號分割。還要這個但因好的字符型放到雙引號中。

下面例子是文字表達式的簡單使用。通常,實際使用中他不會這麼簡單,而是作爲更復雜的表達式,比如說在正則表達式的一側。


ExpressionParser parser = new SpelExpressionParser();

// evals to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();

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

// evals to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();

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

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

10.5.2 Properties, Arrays, Lists, Maps, Indexers

屬性引用是很簡單的:只需要用‘.’表達嵌套的屬性值:


// evals to 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 Array
StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);

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

// Members List
StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);

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

// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
        societyContext, String.class);

Map的例子:


// Officer's Dictionary

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

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

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

10.5.3 Inline lists

Lists可以用{}表達:


// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);

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

{}本身表達一個空的list。處於對性能考慮,如果列表都是由文字組成,系統會創建一個常量列表而不是每個評估都會建一個新的列表。

10.5.4 Inine Maps

Maps可以直接用{key:value}表示:


// evaluates to a Java map containing the two entries
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);

{:}表示一個空的map。爲性能考慮同上。

10.5.5 Array construction

可以用Java數組構建數組,在構造時就可以 初始化填充這個數組:

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

// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);

// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);

當構造多維數組時目前不充許提供初始化。

10.5.6 Methods

方法包括經典的Java語法。

// string literal, evaluates to "bc"
String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);

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

10.5.7 Operators

Relational operators

關係運算符包括“==”“!=”“<””<=” “>” “>=”.

// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);

// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);

除了支持關係運算,SpEL還支持instanceofmatches運算:

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

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

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

每個字符操作可以單純的用字母代替。因爲這可以米麪字符在文檔中有特殊含義。以下是等價的符號: lt (<), gt (>), le (), ge (>=), eq (==), ne (!=), div (/), mod (%), not (!)。不區分大小寫。

Logical operators

邏輯運算符有and,or和not。

// -- AND --

// evaluates to false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);

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

// -- OR --

// evaluates to true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);

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

// -- NOT --

// evaluates to false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);

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

Mathematical operators

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

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

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

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

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

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

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

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

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

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

// Operator precedence
int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21

10.5.8 Assignment

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

10.5.9 Types

T操作符代表java.lang.Class(the type)的一個實例。

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

10.5.10 Constructors

類名不能與基本類型和String一樣。

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

//create new inventor instance within add method of List
p.parseExpression(
        "Members.add(new org.spring.samples.spel.inventor.Inventor(
            'Albert Einstein', 'German'))").getValue(societyContext);

10.5.11 Variables

表達式中的變量的表達式語法#variableName

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"

The #this and #root variables

#this 變量指的是當前的評估對象,#object 變量指的是根上下文對象。

// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));

// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("primes",primes);

// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
        "#primes.?[#this>10]").getValue(context);

10.5.12 Functions

可以通過註冊可以在表達式字符串中調用的用戶定義的函數來擴展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);

10.5.13 Bean references

如果使用bean解析器配置了評估上下文,則可以通過(@)符號在表達式中查找beans:

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

// This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@foo").getValue(context);

要訪問工廠bean本身,需要在bean名稱前加(&)符號:

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

// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);

10.5.14 Ternary Operator (If-Then-Else)

可以使用三元運算符,在表達式中執行if-then-else條件邏輯。一個小例子:

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

在這個例子中,會返回‘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"

10.5.15 Elvis Operator

Elvis操作符縮短了三元操作符,在Groovy語言中使用。在三元操作符中,需要重複一個變量兩次,例如:

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

相反,你可以使用Elvis操作符,命名與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

10.5.16 Safe Navigation operator

安全導航運算符用於避免拋出NullPointerException異常,它來自於Groovy語言。通常在引用一個對象時,需要在訪問對象的方法或屬性之前驗證它是否爲空。爲了避免這種情況,安全導航操作會簡單的返回一個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 - does not throw NullPointerException!!!

10.5.17 Collection Selection

選擇是表達式語言中的一個強大的功能,它允許你通過從一些條目裏選擇將一些源集合轉化爲另外一個。

選擇用的語法是.?[selectionExpression]。它會過濾掉這個集合,然後返回一個包含原始元素子集的新集合。例如,選擇將很快得到Serbian發明者的列表:

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

列表和map都可以進行選擇。在前一種情況下,根據單獨列表元素進行評估標準,同時針對map,根據每個map的entry進行評估標準(Java類型Map.Entry)。Map輸入中有key和value,可以作爲選擇中訪問的屬性。

下面這個表達式將會返回一個由原始元素集合組成的新集合,其中value都小於27:

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

爲了返回所有選定元素,還可以檢索第一個或最後一個值。匹配第一個元素的語法^[...],匹配最後一個元素的語法是$[...]

10.5.18 Collection Projection

Projection(翻譯不出來)允許一個集合驅動子表達式的評估,返回一個新的集合,它的語法是![projectionExpression]。更容易理解的例子,假設有一個inventors的列表,我們想找到他們出生城市的列表。我們將會評估每個inventor輸入‘placeOfBirth.city’。使用projection:

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

當然,map也可以使用,同上。

10.5.19 Expression templating

表達式模板允許文本域一個或多個評估塊混合一起。每個評估模塊都可以用自定義的前綴和後綴進行分隔,常用#{}作爲分隔符。例如:

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

// evaluates to "random number is 0.7038186818312008"
public class TemplateParserContext implements ParserContext {

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

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

    public boolean isTemplate() {
        return true;
    }
}

10.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;
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章