Spring Expression Language(SpEL) spring表達式語言

10.Spring Expression Language(SpEL) spring表達式語言

10.1 介紹

SPEL語言是一個很是強力的支持運行時查詢和操做對象圖譜的語言.這個語言語法和傳統EL表達式類似,但提供了許多額外功能,最重要的是函數調用和基本字符串模板函數;html

雖然還有其餘的java表達式語言,如OGNL,MVEL,JBoss EL等,但SPEL是由spring社區獨家建立並支持的,並能在spring產品集中的全部產品裏使用的語言.它的語言功能是基於spring項目集的產品需求驅動,也包括工具的需求,這塊主要是指基於eclipse的Spring Tool Suite.也就是說,SpEl是基於技術無關的API,並容許其餘須要加入的語言實現加入並將之集成.java

SpEL服務是spring項產品中表達式工具的基礎,它沒有直接與spring綁定並可獨立使用.爲了可以自適用,本章中全部例子在使用SpEL時都看成獨立的表達語言.這要求建立一系列的如parser等的啓動組件類.大部分的spring用戶不須要處理這些組件,僅需將程序員的表達式字符串進行解析.典型使用的例子是將SpEL集成到xml或基於註解的bean定義裏,詳情查看 Expression support for defining bean definitions;程序員

本章主要介紹該語言的主要功能,API和語法.有些地方,發明和發明新類將做爲表達目標的替換對象.操做這些類的聲明和數據,已在本章末尾列明.正則表達式

10.2 功能概覽

有如下功能:spring

  • Literal expressions 字面表達式
  • Boolean and relational operators 布爾和關係運算符
  • Regular expressions 正則表達式
  • Class expressions 類表達式
  • Accessing properties,arrays,lists,maps
  • Method invocation 方法調用
  • Relational operators 關係運算
  • Assignment 參數
  • Calling constructors 調用構造器
  • Bean references bean引用
  • Array construction 數組構造器
  • Inline lists 內聯lists
  • Inline maps 內嵌mps
  • Ternary operator 三元運算符
  • Variables 變量
  • User defined functions 用戶定義函數
  • Collection projection 集合投影
  • Collection selection 集合篩選
  • Templated expressions 模板表達式

10.3 使用SpEL接口的表達式替換

本節介紹了SpEL接口的簡單使用和它的表達語言.該語言的文檔在Language Reference中可見. 下面的代碼介紹了SpEL API如何處理字面字符串表達式'HELLO World'.express

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

這個message變量的值是簡單的'Hello World'.編程

你最經常使用的SpEL類和接口在包org.springframework.expression和他的子包spel.support裏.數組

ExpressionParser主要負責轉化一個字符串表達式 .上個例子中這個表達式字符串是一個有單引號包圍的字符串字面值.Expression接口負責轉化以前定義好的表達式字符串.當分別調用parser.parserExpression,exp.getValueException時,會拋出兩種異常:ParseException和EvaluationException緩存

SpEL還支持其餘功能,例如調用方法,訪問屬性,調用構造器.安全

下面是一個方法調用的例子,你能夠調用字面值字符串的concat方法.

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

消息的值如今是'Hello World!'.

下個例子是調用string的屬性Bytes,以下;

ExpressionParser parser =new SpelExpressionParser();

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

Spel還支持使用點符號來訪問內嵌的屬性,例如 prop1.prop2.prop3,還能夠設置屬性的值.公共的字段也能夠訪問:

ExpressionParser parser = new SpelExpressionParser();

//invokes 'getBytes().length'
Expression exp=parser.parserExpression("'Hello World'.bytes.length");
String message=exp.getValue(String.class);

記住泛型方法public<T> getValue(Class<T> desiredResultType)的使用.使用該方法能夠省去表達式的值向目標類型轉化的過程.若是該值沒法轉化成T類型或者沒法經過已註冊的轉化器轉化,則會拋出EvaluationException的異常.

從實例裏來獲取值轉化

從一個特定實例裏獲取值轉化是比較常見的用法(這個實例也叫根對象).這裏有兩個方法,取決於你要轉化表達式值的對象是否會常常變化.下面是從一個轉化類實例裏轉化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".

若是該特定對象的值常常變化,則須要直接從對象裏獲取值:

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

在此狀況下getValue直接提供給getValue和表達式組件的轉化器tesla建立和管理一個相應的默認轉化上下文--這個上下文不是必需的.

StandardEvaluationContext相對來講構造比較昂貴,在重複環境下,它被構形成緩存狀態,這樣能夠保證經常使用的表達式轉化工做更快.所以最好儘量的緩存和重用它們,而不是爲每一個表達式轉化構造一個新的.

在一些場景下,你最好使用一個配置的轉化上下文,並會爲每一個getValue調用提供不一樣的根對象.getValue容許指定相同的調用.在這個場景下被調用的根對象被認爲是在轉化上下文中重寫了特定的值.

[注意]

在單獨使用SpEL時,這裏須要建立parser,parser expression,可能還有轉化上下文以及根對象.可是,更常見的用法是把SpEL表達式做爲配置文件的一部分,例如spring bean或者spring Web Flow 定義.在這種狀況下,parser,evaluation context,根對象和其餘已定義對象都是未明確的定義,所以要求用戶只需指定表達式.

最後一個入門例子,是使用上個例子中的轉化器對象展現的布爾操做符的例子;

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

10.3.1 the EvaluationConext interface

接口EvaluationContext會被使用當轉化一個表達式爲相應屬性,方法,字段,或執行類型轉化.其一個開箱即用的實現,StandardEvaluationContext,使用反射操做對象,緩存了java.lang.reflect.Method,java.lang.reflect.Field,以及java.lang.reflect.Constructor實例來應付更多的操做.

StandardEvaluationContext能夠經過setRootObject()或把根對象加入構造器來指定其要轉換的根對象.你能夠經過setVariable()和registerFunction()來指定表達式可能用到的函數和變量.變量和方法使用在變量和函數一節裏有描述.你能夠在StandardEvaluationContext中註冊自定義ConstructorResolvers,MethodResolvers,PropertyAccessors來擴展SpEL轉化表達式.能夠參看這些類的javaDoc查看更多細節.

類型轉化 Type Conversion

SpEL默認使用spring core(org.springframework.core.convert.ConversionService)中的轉化服務.這個轉化服務帶着許多內置的轉化器,但它有很好的擴展性,你能夠添加不一樣類型之間的自定義轉換.另外他的核心功能是泛型感知.這意味着當表達式中有泛型類型時,SpEL會嘗試轉化它遇到的任何對象以保證類型正確.

這個在實際中意味着什麼?嘗試用setValue()方法指定一個List的屬性.這個屬性的類型是List<Boolean>.SpEL會意識到list裏的元素在被替換以前須要轉化爲布爾類型.簡單例子以下:

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

simple simple=new Simple();

simple.booleanList.add(true);

StandardEvaluationContext simpleContext=new StandardEvaluationContext(simple);

//false這裏是一個字符串;然SpEL須要在它插入集合以前轉換爲布爾類型

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

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

10.3.2 Parser configuration 轉化器配置

你可使用轉化器配置對象(org.springframework.expression.spel.SpelParserConfiguration)來配置SpEL 表達式解析器.這個配置對象能夠控制一些表達式組件的行爲.例如,若是數據爲索引指定的索引處元素是數組或集合的元素且是null;它能夠自動地建立的元素.當用表達式來構建屬性引用表達鏈會很是有用.若是指向數組或集合的下標且指定的下標在數組或集合當前容量以外,那麼數組或集合會自增加以容納這個下標.

lass 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 compilation

spring 4.1引入了基本屬性編譯器.表達式在轉化中提供了達量的靈活性但不提供最佳性能.表達式用法通常很好,但被如spring Integration等組件使用時,雖然地位很重要,但對其活力沒有真實的追求.

這個新的SpEL編譯器傾向於表達這種需求.這個編譯器會在表達式行爲執行以前飛速產生一個真實的java類,並用該類實現更快的表達式轉換.因爲缺乏表達式類型,這個編譯器會編譯時翻譯轉化表達式時進行信息收集.固然,若是期間表達式元素的類型改變,這會給基於編譯的信息帶來困擾.所以,編譯最適用於那種類型信息在重複替換中不會改變的表達式.

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

someArray[0].someProperty.someotherProperty<0.1

這涉及數組訪問,一些屬性訪問和數值運算,性能表現很是明顯.在一個運行5千次迭代的維基準測試裏,若是隻是用解釋器須要75毫秒轉化值,而若是使用可編譯的表達式則只需3毫秒.

Compile configuration 編譯配置

編譯器默認不會開啓,可是有兩種方式來開啓它.它能夠被先前提到的轉化器配置開啓或當SpEL在其餘組件內使用能夠經過系統屬性開啓.這節將討論着兩種方法.

很是重要來接力編譯器操做的各類模式,能夠在一個枚舉裏獲取(org.springframework.expression.spel.spelCompilerMode),這些模式以下:

  • OFF 編譯器關閉,這是默認的

  • IMMEDIATE (極速) 在極速模式下表達式會被儘量的編譯.它通常在第一次解釋器編譯以後.若是編譯表達式出錯(通常都是類型變化),那麼這個表達式轉化的調用者會受到一個異常.

  • MIXED 混合模式下表達式在解釋和編譯模式之間悄悄轉化.當一些解釋器運行時,它戶轉化成編譯模式.若是編譯模式出了寫錯誤,則自動轉化爲解釋模式.有時他會產生其餘形式的編譯模式並轉化爲它.基本上用戶在混合模式中遇到的異常會相應的進行替換處理.

極速模式之因此存在是由於混合模式產生的問題會對錶達式產生負面影響.若是一個編譯的表達式在部分紅功以後,就會對系統狀態形成影響.若是這樣的話,調用者不想讓它在解釋模式下安靜的重複運行,由於有些表達式可能會運行兩次.

在選擇一個模式,使用SpelParseConfiguration來配置這個轉化器:

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

SpelExpressionParser parser=new SpelExpressinParser(config);

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

MyMessage message=new MyMessage();

Object payload = expr.getValue(message);

在指定編譯模式時還能夠指定一個類加載(也能夠爲空).編譯的表達式會定義在提供的類加載器的子加載器下.保證同一個類加載器很是重要,由於它能夠看到表達式轉化過程當中的所欲類型.若是沒有特定的,那麼一個默認的類加載器就會使用(一般是當前線程表達式轉化執行的上下文類加載器).

第二種方式配置編譯器是當在其餘組件內部使用SpEL時,你不可能經過一個配置對象來配置它.在這種狀況下,可使用系統屬性.spring.expression.compiler.mode屬性能夠設置爲SpelCompilerMode枚舉的一個值(off,immediate,mixed等).

Compiler limitations (編譯器的侷限)

spring 4.1以後基本的編譯框架已經替換了.可是這個框架並不支持編譯全部種類的表達式.出發點是通用的表達式在性能關鍵的上下文中使用.當前這些表達式的種類不會被編譯:

  • expressions involving assignment 設計賦值的表達式
  • expressions relying on the conversion service 依賴轉化服務的表達式
  • expression using custom resolvers or accessors 依賴自定義轉化器或訪問的表達式
  • expressions using selection or projection 使用篩選或投影的表達式

將來有更多類型的表達式會被編譯

10.6 Classes used in the examples

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

}

10.4 Expresson support for defining bean definitions 表達式對bean定義的支持

SpEL表達式能夠在xml或註解配置bean定義中使用.在這兩種狀況下多表達式都是#{<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}"/>

   其餘屬性
</bean>

變量systemProperties是提早定義的,因此你能夠在你的表達式中使用它.記住在這種狀況下你不須要定義#標誌前綴.

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

    <!-- other properties -->
</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>

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

}

在自動裝配的方法和構造器上使用@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;
    }

    // ...
}

10.5 Language Reference 語言引用

10.5.1 literal expression 字面值表達式

字面值表達式類型支持字符串,時間,數值(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();

數字上支持負號,字數負號,小數點的使用.默認真實數字會被Double.parseDouble()轉化.

###10.5.2 Properties,Arrays,List,Maps,Indexers 屬性,數組,集合,maps,索引器

執行屬性引用很是容易:只需使用一個句號來標誌一個內嵌屬性.Inventor類的實例,pupin和tesla,能夠在類中使用的例子中查看列表的數據操做.要向下指引,獲得Tesla的出生年份和Pupin的出生地能夠用之前用過的表達式.

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

String city=(String)parser.parserExpression("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.parserExpression("Members[0].Inventions[6]").getValue(societyContext,String.class);

maps裏的內容能夠被括號裏的指定了字面值的key值訪問.這種狀況,適用於當前的名爲Officers的map的key都是String類型的,全部咱們能夠指定字符串字面量.

// 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.parserExpression("{{'a','b'},{'v','d'}}").getValue(context);

{}意味着只是一個空集合.出於性能緣由,若是這個集合徹底有固定的字面值組成,那麼會建立一個常量的list來表示這個表達式,而不是爲每一個轉化都建一個新的list.

10.5.4 Inline Maps 內嵌的maps

Maps能夠被{key:value}的形式直接表示.

//evaluates to a Java map containing the two entries
Map inventorInfo =(Map)parser.parserExpression("{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自身由固定的文字或其餘內嵌常量結構(lists or maps )組成,那麼一個常量的map就會構建來表示這個表達式,而不是爲每一次轉化都建立一個新的map.map的key定義爲引用類是可選的,上面的例子沒有使用引用key.

10.5.5 Array construction (數組構造器)

數組可使用java語法建立,額外提供了一個初始器,能夠在構造期間來操做這個數組.

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

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

//Multi dimensional array 多維數組
int[][] numbers3 = (int[][])parser.parserExpression(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);

//evaluate 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還支持instanceof和基於matches操做的正則表達式.

//evaluates to false
boolean falseValue=parse.parserExpression("'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})?$'");

注意:

要特別注意java的原生類,它們會被轉化爲包裝類,因此依次預測 1 instanceof T(int) 判斷是false,而1 instanceof T(Integer) 判斷爲true.

每個符號操做符能夠指定爲一個等效的字母.這能夠避免在一些文檔類型中有其餘特定意義的符號,那麼相應的表達式會沒法使用(例如在一個xml文檔中).文字替換符以下:lt(<),gt(>),le(<=),ge(>=),eq(==),ne(!=),div(/),mod(%),not(!).它們不區分大小寫.

Logical operators 邏輯運算

邏輯運算支持且或非操做.它們的用法以下:

//--AND--

//evaluates to false
boolean falseValue=parser.parserExpression("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.parserExpression("'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);//6
double tweentyFour = 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 賦值

參數的設置能夠用指定操做完成.通常能夠經過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);

10.5.9 Types 類

特殊的T操做符能夠用來指定一個java.lang.Class的實例.這個操做符也能夠調用靜態方法.StandardEvaluationContext使用Typelocator來查找類,且StandardTypeLocator(可替代)創建在對java.lang包的理解上.這意味着在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);

能夠用來調用靜態方法和常量.

10.5.10 Contructors(構造器)

構造器可使用new操做符調用.除了原生類和String類外,須要使用類的全名稱;

Inventor einstein = p.parserExpression("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來引用.變量能夠經過StandardEvaluationContext中的setVariable方法設置.

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 變量

'#this' 變量用來定義和指向當前的處理對象(不合格的引用將會解決).變量#root一般定義和指向根級的上下文對象.#this通常經常使用語表達式組件的轉化,而#root則指向上一層次.

//create an array of integers
List<Integer> primes=new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,4,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.setVaribale("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類的registerFunction()方法註冊.

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 hellworldReversed=parser.parseExpression("#reverseString('hello world')").getValue(context,String.class);

10.5.13 Bean references bean引用

若是轉換上下文配置了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);

要獲得一個工廠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) 三元操做符

你可使用三元運算符來進行表達式裏的三元操做的條件邏輯判斷.一個例子以下:

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

如上,這個布爾的返回結果是'falseExp'.一個更加複雜的例子以下:

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

expression="isMember(#queryName)?#queryName+'is a member of the '+Name+' Society':#query+ '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操做符來查看三目運算符更簡潔的語法.

10.5.15 The Elvis Operator 埃爾維斯算子

埃爾維斯算子是對三元操做的簡化並能夠在Groovy語言中使用.使用一個三元運算符,通常你要操做一個變量兩次.例如:

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

埃爾維斯算子的命名來源於埃爾維斯的髮型.name?:'Unkown';等同於 name=name!=null?name:"Unknown";

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的值
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);

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

10.5.16 Safe Navigation operator

安全導航操做來自於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!!!

注:埃爾維斯算子能夠用來申請表達式中的默認值,例子:

@Value("#{systemProperties['pop3.port'] ?: 25}")

若是系統屬性pop3.port爲空,則定義25.

10.5.17 Collection Selection 集合篩選

篩選是一個強有力的表達式語言功能,容許你經過條目選擇從一些資源集合轉化到另外一些資源集合.

篩選使用語法.?[selectionExpression].它會攔截集合並返回一個包含了原始元素的子集的新集合.例如,篩選能夠輕易得到集合中的叫Serbian的發明家.

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

篩選能夠在list和map上適用.在這種形式下,篩選條件適用於每一個list中的個體.而map的匹配則是匹配操做Map中的每一個鍵值對(Map.Entry的對象).map的鍵值對訪問同篩選中使用的屬性同樣.

這個表達式會返回一個新的有原來的map中的元素值都小於27的元素組成的新的map.

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

除了返回全部的元素以外,還能夠返回第一個或最後一個值.要獲得第一個匹配的值可使用^[...]的語法,獲得最後一個則使用$[...]語法.

###10.5.18 Collection Projection 集合投影 投影容許一個集合驅動子表達式的轉化,併產生一個新的集合.投影的語法以下:![projectionExpression].舉個容易理解的例子,假設咱們有一個發明家的集合,但咱們想要一個他們想要出生城市的列表.最有效的是咱們獲得發明家列表裏的每一個實體的'placeOfBirth.city'.如此使用:

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

一個map也可使用投影,這種狀況下,投影表達式能夠轉化map中每一個實體.map投影的結果是包含了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"

這個字符串是由字面值字符串'random number is' 和在#{}分隔符裏表達式轉化後合併的結果,分割符裏的表達式調用了random()方法.parserExpression()方法的第二個參數的類型是ParserContext.這個ParserContext接口能夠來改善表達式的轉化以支持表達式模板功能.TeplateParserContext定義以下:

public class TemplateParserContext implements ParserContext{
	public String getExpressionPrefix(){
		return "#{";
	}
	public String getExpressionSuffix() {
        return "}";
    }

    public boolean isTemplate() {
        return true;
    }
}
相關文章
相關標籤/搜索