spring-core-4 Spring EL表達式

4.1 介紹

Spring EL表達是一種強大的表達式語言, 可以支持在運行時查詢和操作對象.它與EL類似, 但提供了更多擴展功能,如方法調(diào)用,字符串模板功能.
Spring el表達式支持以下功能:

  • 字面值表達式
  • boolean和關(guān)系操作符
  • 正則表達式
  • 類表達式
  • 訪問屬性,數(shù)組,集合
  • 方法調(diào)用
  • 關(guān)系運算
  • 調(diào)用構(gòu)造函數(shù)
  • Bean引用
  • 構(gòu)建數(shù)組
  • 內(nèi)聯(lián)列表
  • 內(nèi)聯(lián)map
  • 三元操作
  • 變量
  • 用戶定義函數(shù)
  • 集合選擇
  • 模板表達式
4.2 計算

SpEL相關(guān)的類和接口位于org.springframework.expression包中.
ExpressionParser接口負(fù)責(zé)解析表達式字符串(用單引號包裹起來).
Expression接口負(fù)責(zé)計算表達式字符串.
此處可能拋出兩個異常:ParseException和EvaluationException`.
SpEL支持多種功能,如調(diào)用方法, 訪問屬性,調(diào)用構(gòu)造函數(shù).
調(diào)用方法示例:

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

訪問屬性示例:

ExpressionParser parser = new SpelExpressionParser();
// 此時會調(diào)用getBytes()方法
Expression exp = parser.parseExpression("'Hello World'.bytes");
byte[] bytes = (byte[]) exp.getValue();

SpEL也支持嵌套屬性.如p1.p2.p3
示例:

ExpressionParser parser = new SpelExpressionParser();
// 會調(diào)用 getBytes().length
Expression exp = parser.parseExpression("'Hello World'.bytes.length");
int length = (Integer) exp.getValue();

調(diào)用構(gòu)造函數(shù)示例:

ExpressionParser parser = new SpelExpressionParser();

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

注意: 使用public <T> T getValue(Class<T> clazz);方法可以無需強轉(zhuǎn).但是如果結(jié)果轉(zhuǎn)換為T類型失敗,則會拋出EvaluationException.

SpEL最常見的用法是根據(jù)一個特定的對象實例(稱為根對象)求值.

// 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);
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true
4.2.1 EvaluationContext

EvaluationContext接口在計算表達式時解析屬性,方法,字段及提供類型轉(zhuǎn)換.它有兩個開箱即用的實現(xiàn):

  • SimpleEvaluationContext: 提供了部分SpEL配置項.它只支持SpEL的一個子集,它不支持JAVA類型引用,構(gòu)造函數(shù),及bean的引用. 要求顯式的配置對表達式中方法和屬性的支持級別.默認(rèn)情況下, 僅支持讀取屬性.但可以通過獲取一個builder來具體配置所需要的支持:如自定義PropertyAccessor(無反射), 數(shù)據(jù)綁定屬性的只讀訪問或讀寫.
  • StandardEvaluationContext: 提供了全部的SpEL配置項

類型轉(zhuǎn)換
默認(rèn)情況下SpEL使用org.springframework.core.convert.ConversionService作為類型轉(zhuǎn)換服務(wù).
示例:

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

Simple simple = new Simple();
simple.booleanList.add(true);

EvaluationContext context = SimpleEvaluationContext().forReadOnlyDataBinding().build();

// 此處false是一個字符串. SpEL and the conversion service 將正確識別它并將其轉(zhuǎn)換為Boolean型.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");

// b will be false
Boolean b = simple.booleanList.get(0);
4.2.2 轉(zhuǎn)換配置

可以使用SpelParserConfiguration對象配置ExpressionParser. 配置對象能控制一些表達式組件的行為.如對于數(shù)組或集合, 指定其某個元素為null, 則可以自動創(chuàng)建這個元素,如果索引超過了數(shù)組或列表的長度, 則會自動增加數(shù)組或列表以適應(yīng)這個索引.

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
4.2.3 SpEL編譯

spring 4.1引入了一個基本的表達式編譯器, 編譯器將在表達式計算期間動態(tài)地生成一個真正的java類, 以便更快的解析表達式. 由于編譯器無法從表達式中知道引用的屬性的類型, 但是在第一次運行時是可以知道的,當(dāng)然,如果你表達式引用的屬性類型會經(jīng)常變化, 這可能會帶來麻煩, 所以編譯只適合用于表達式引用的屬性類型不常變化的場景.
對于以下表達式:

someArray[0].someProperty.someOtherProperty < 0.1

這包含了數(shù)組訪問,屬性引用和數(shù)值操作.編譯后的性能提升非常明顯,在50000次迭代中, 不編譯需要75ms來完成, 編譯后只需要3ms.

編譯器配置
編譯器默認(rèn)是關(guān)閉的,但是它可以通過parser配置來開啟或一個系統(tǒng)屬性來開啟.
spring定義了幾種編譯器的操作模式(expression.spel.SpelCompolerMode):

  • OFF: 關(guān)閉編譯器
  • IMEDIATE: 立即模式, 表達式盡可能快的被編譯,這通常是在第一次計算表達式之后完成的,如果編譯失敗則會拋出異常.
  • MIXED: 混合模式, 在解釋模式(編譯器關(guān)閉時所用的方式)與編譯模式之間自動切換, 如果編譯模式下發(fā)生了錯誤,則會自動切換到解釋模式.
    IMEDIATE模式的存在是由于MIXED模式可能會存在副作用, 比如在MIXED模式下,表達式在編譯模式下運行了一半,就出現(xiàn)了異常,這時可能對某些系統(tǒng)狀態(tài)作了更改,此時它會自動切換到解釋模式下,之前運行過的部分會被再運行一次,因此這時可能會引發(fā)某些問題.
    示例, 使用SpelParserConfiguration配置編譯模式:
SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE, this.getClass().getClassLoader());
SpelExpressionParser parser = new SpelExpressionParser(config);
Expression exp = parser.parseExpression("payload");
MyMessge message = new MyMessage();
Object payload = exp.getValue(message);

在指定編譯器模式時也可以指定一個類加載器(可以為null), 編譯后的表達式將在提供了類加載器創(chuàng)建的子類加載器中定義.重要的是要確保提供的類加載器能夠看到表達式計算過程中所要調(diào)用的所有類.如果沒有提供, 則使用默認(rèn)加載器(通常在運行時線程所在的上下文類加載器).

另一種配置方式是當(dāng)SpEL在其他組件中使用時可能無法通過上面的方式進行配置,這時可以通過系統(tǒng)屬性spring.expression.compiler.mode來配置(值為前面提到的三種之一).

編譯器的限制
spring提供的編譯器不以編譯所有類型的表達式.以下幾種不被支持:

  • 賦值表達式
  • 依賴于轉(zhuǎn)換服務(wù)的表達式
  • 使用自定義解析器或訪問器的表達式
  • expressions using selection or projection
4.3 bean定義中的表達式

SpEL可以用在XML或注解配置中.語法格式為#{ expression }.

4.3.1 XML配置

示例: 屬性或構(gòu)造函數(shù)中使用

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

使用systemProperties, 它是預(yù)定義的, 可以直接使用且不用在其前面添加#.如下:

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

通過bean名稱引用另一個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.3.2 注解配置

在字段或方法上, 或者方法或構(gòu)造函數(shù)的參數(shù)中使用@Value注解.

public static class FieldValueTestBean
    // 在字段上使用
    @Value("#{ systemProperties['user.region'] }")
    private String defaultLocale;

    // setter getter
}

如下示例與上面示例等價:

public static class PropertyValueTestBean

    private String defaultLocale;

    // set方法上使用
    @Value("#{ systemProperties['user.region'] }")
    public void setDefaultLocale(String defaultLocale) {
        this.defaultLocale = defaultLocale;
    }

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

}

在@Autowired注解的構(gòu)造函數(shù)參數(shù)中使用:

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

}
4.4 語法參考
4.4.1 字面值表達式

字面值表達式支持String, 數(shù)字(整數(shù),實數(shù),十六進制), boolean, null.字面值要用單引號分隔, 要將單引號本身也引入其中,請使用兩個單引號.通常不會單獨這樣使用,而是將其作為復(fù)雜表達式的一部分:

ExpressionParser parser = new SpelExpressionParser();

// 返回"Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
// 實數(shù)
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();

數(shù)字支持使用負(fù)號,指數(shù)符號和小數(shù)點.

4.4.2 屬性, 數(shù)組, 集合, Map, 索引

訪問屬性,用.符號.允許對屬性名的第一個字母不分大小寫.

int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);

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

數(shù)組或List

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// Inventions Array

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

// Members List

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

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

訪問Map, 由于Map的Key為String類型,因此可以直接指定key值.

// 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");
內(nèi)聯(lián)List

可以內(nèi)聯(lián)使用{}直接表示List:

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

注:{}本身就表示一個空的集合.

4.4.4 內(nèi)聯(lián)Map

Map也可以通過{key:value}格式來直接定義.

// 將會得到包含兩個元素的java Map
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.Map的key上的引號是可選的.

4.4.5 構(gòu)建數(shù)組

可以用熟悉的java語法來構(gòu)建數(shù)組,同時也可提供初始化值.

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

注:日前不支持在構(gòu)建多維數(shù)據(jù)時提供初始化值.

4.4.6 方法調(diào)用

通過java語法格式, 可以直接對字符串調(diào)用方法,也支持可變參數(shù).

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

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

關(guān)系操作符
關(guān)系操作符包括:==, !=, <, <=, >, >=.

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

大于或小于比較null值時遵循如下規(guī)則, null表示什么也沒有,因此任何值X與null進行X>null都將為true, 反之也都將為false. 在使用數(shù)值比較時,請不要與null進行比較, 而是與0進行比較.

除了標(biāo)準(zhǔn)的關(guān)系運算符之外, SpEL還支持使用instanceof和正則表達式匹配計算:

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

在對基本類型使用instanceof時要注意, 因為它會被裝箱,所以1 instanceof intfalse, 而1 instanceof Integertrue.
操作符可以用字母來代替, 這在xml配置中可以避免沖突.其字母代替為: lt(<), gt(>), le(<=), ge(>=), eq(==), ne(!=), div(/), mod(%), not(!). 這些不區(qū)分大小寫.

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

數(shù)學(xué)操作
加號可以在操作數(shù)字和字符串, 減號,乘號和除號, 取余, 冪運算只支持?jǐn)?shù)字.

// 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
4.4.8 賦值操作

設(shè)置屬性是通過賦值操作符來完成的,這通常是在setValue中完成的,但也可以在getValue中完成.

Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic");

// alternatively
String aleks = parser.parseExpression(
        "Name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);
4.4.9 類

特殊的T操作符可用來表示一個類的實例, 靜態(tài)方法也是用它來調(diào)用的.T操作符可以自動發(fā)現(xiàn)java.lang包中的類,因此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);
4.4.10 構(gòu)造函數(shù)

可以通過new操作符調(diào)用構(gòu)造函數(shù).除了基本類型和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);
4.4.11 變量

可以使用#varName格式引用變量.

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

EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");

parser.parseExpression("Name = #newName").getValue(context, tesla);
System.out.println(tesla.getName())  // "Mike Tesla"

#this和#root
#this總是表示當(dāng)前的evaluation對象.#root總是代表根上下文對象.雖然#this可能會隨著表達式組件的變化也變化, 但#root卻總是指向根對象的.

// 創(chuàng)建一個Integer類型的數(shù)組
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();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
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);
4.4.12 函數(shù)

你可以通過定義能在表達式字符串中被調(diào)用的自定義函數(shù)來擴展SpEL.

Method method = ...;

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);
public abstract class StringUtils {

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

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
        StringUtils.class.getDeclaredMethod("reverseString", String.class));

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

如果配置了evaluation 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);

如果要訪問factory bean本身, 應(yīng)使用&標(biāo)識符.

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);
4.4.14 三元操作符
String falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String.class);
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"
```

######4.4.15 Elvis 操作符
這是三元操作符的簡寫形式
```
ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("name?:'Unknown'").getValue(String.class);
System.out.println(name);  // 'Unknown'
```
> 可用它來為屬性設(shè)置默認(rèn)值`@Value("#{systemProperties['pop3.port'] ?: 25}")`

######4.4.16 安全導(dǎo)航操作
當(dāng)引用一個對象的屬性時通過要對其作null判斷, 如果為null, 則會拋出NPE, 安全導(dǎo)航操作則是為了避免NPE, 并返回簡單的null.
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

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

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

tesla.setPlaceOfBirth(null);
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
System.out.println(city);  // null - does not throw NullPointerException!!!

######4.4.17 集合選擇
集合選擇是一個強大的表達式語言特性, 它允許你選擇源集合中的一些元素來組成一個新的集合.
其語法格式為:`.?[expression]`.這會過濾集合并返回包含原集合的一個子集的新集合.
```
List<Inventor> list = (List<Inventor>) parser.parseExpression(
        "Members.?[Nationality == 'Serbian']").getValue(societyContext);
```
在列表和Map上都可以使用,list是對每個元素進行選擇, map則是對每個entry進行選擇.因此Map可以將key或value作為表達式屬性進行選擇.
如下,選擇集合的value小于27的值.
```
Map newMap = parser.parseExpression("map.?[value<27]").getValue();
```
**注:**除了返回被選擇的元素, 還可以檢索第一個或最后一個元素.其語法分別是`.^[expression]`和`.$[experssion]`.

######4.4.18 集合Projection
Projection允許一個集合驅(qū)動一個子表達式并返回一個新的集合, 其語法是: `.![expression]`. 
假設(shè)我們有一個inventors list, 現(xiàn)在我們想要找出他們出生的城市的集合.
```
// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");
```
針對Map也可以使用.獲取的結(jié)果是一個list.

######4.4.19 表達式模板
表達式模板是指可以通過表達式前綴和后綴來組合表達式.
```
String randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}",
        new TemplateParserContext()).getValue(String.class);
```
這個表達式的執(zhí)行結(jié)果是將`random number is`和`#{}`中的執(zhí)行結(jié)果拼接起來, 第二個參數(shù)是一個`ParserContext`類型, 用來定義表達式的解析方法, 其定義如下:
```
public class TemplateParserContext implements ParserContext {

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

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

    public boolean isTemplate() {
        return true;
    }
}
```

#####4.5 前面示例中使用的類
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;
    }

}
```
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末零院,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子撰茎,更是在濱河造成了極大的恐慌打洼,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炫惩,死亡現(xiàn)場離奇詭異诡必,居然都是意外死亡搔扁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門扭勉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涂炎,“玉大人设哗,你說我怎么就攤上這事≌痃裕” “怎么了战虏?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長巡社。 經(jīng)常有香客問我晌该,道長,這世上最難降的妖魔是什么次企? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任潜圃,我火速辦了婚禮谭期,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘踏志。我一直安慰自己胀瞪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布圆雁。 她就那樣靜靜地躺著伪朽,像睡著了一般汛蝙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坚洽,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天讶舰,我揣著相機與錄音,去河邊找鬼。 笑死援所,一個胖子當(dāng)著我的面吹牛住拭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播杠娱,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼谱煤,長吁一口氣:“原來是場噩夢啊……” “哼刘离!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起茧痕,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤恼除,失蹤者是張志新(化名)和其女友劉穎豁辉,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體彩掐,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡堵幽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年弹澎,在試婚紗的時候發(fā)現(xiàn)自己被綠了苦蒿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡团滥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拱燃,到底是詐尸還是另有隱情力惯,我是刑警寧澤父晶,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站尝苇,受9級特大地震影響埠胖,放射性物質(zhì)發(fā)生泄漏押袍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一汽馋、第九天 我趴在偏房一處隱蔽的房頂上張望圈盔。 院中可真熱鬧,春花似錦铁蹈、人聲如沸众眨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颂龙。三九已至,卻和暖如春躲叼,著一層夾襖步出監(jiān)牢的瞬間企巢,已是汗流浹背包斑。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工涕俗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留再姑,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓绍填,卻偏偏與公主長得像栖疑,于是被迫代替她去往敵國和親遇革。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容