Spring生態研習【二】:SpEL(Spring Expression Language)


1. SpEL功能簡介

它是spring生態里面的一個功能強大的描述語言,支在在運行期間對象圖里面的數據查詢和數據操作。語法和標准的EL一樣,但是支持一些額外的功能特性,最顯著的就是方法調用以及基本字符串模板函數功能。

SpEL是spring的產品列表中的基本功能。

 

2. 特性概要

Literal expressions
Method invocation
Accessing properties, arrays, lists, maps
Inline lists
Array construction
Relational operators
Assignment
Class Expression
Constructors
Variables
Ternary Operator (If-Then-Else)
Safe Navigation operator
Collection Selection
Collection Projection
Expression templating

 

3. 基本語法結構

#{ } 標記會提示Spring 這個標記里的內容是SpEL表達式。 當然,這個可以通過Expression templating功能擴展做改造
#{rootBean.nestBean.propertiy} “.”操作符表示屬性或方法引用,支持層次調用
#{aList[0] } 數組和列表使用方括號獲得內容
#{aMap[key] } maps使用方括號獲得內容
#{rootBean?.propertiy} 此處"?"是安全導航運算符器,避免空指針異常
#{condition ? trueValue : falseValue} 三元運算符(IF-THEN-ELSE)
#{valueA?:defaultValue} Elvis操作符,當valueA為空時賦值defaultValue

 

4. 基本案例介紹

我這里要介紹的SpEL的特性案例,都是在Spring-boot 1.5.4的環境下進行的。SpEL不一定要是基於Web的,我們就在純后台程序的環境下,驗證這個SpEL的神秘而強大的特性。另外,SpEL默認的日志輸出是SLF4j,我不習慣也不喜歡,我將其改造成了Log4j,改造配置也很簡單,主要是pom.xml里面將Spring-boot默認的日志配置exclude掉。

 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.roomdis</groupId>
    <artifactId>springel</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>


    <name>Spring Expression Language</name>
    <description>Spring boot project</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Spring boot支持log4j,並停掉spring-boot默認的日志功能 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> <exclusion> <artifactId>log4j-over-slf4j</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency>

        <!-- 添加log4j的依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j</artifactId> <version>1.3.8.RELEASE</version> </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

 

 

 

4.1 Literal expressions

@SpringBootApplication
public class LiteralExpressionApplication {
    static Logger logger = Logger.getLogger(LiteralExpressionApplication.class);
    public static void main(String []args) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression("'Hello World'");
        String message = (String) exp.getValue();
        logger.info(message);
    }
}

輸出的內容為:Hello World
接口ExpressionParser主要用來解析表達式字符串,在這個例子中,表達式字符串就是上面單引號里面的內容。接口Expression主要用來評估前面的表達式字符串。這里,調用parser.parseExpression可能拋出異常ParseException,調用exp.getValue可能拋出異常EvaluationException。

 

4.2 Method invocation

@SpringBootApplication
public class MethodInvocationApplication {
    static Logger logger = Logger.getLogger(MethodInvocationApplication.class);
    public static void main(String []args) {
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression("'Hello World'.concat('!')");
        String message = (String) exp.getValue();
        logger.info(message);
    }
}

輸出的內容為:Hello World!
表達式中,字符串'Hello World'進行了函數調用concat,將字符串'!'進行了拼接,最終得到了一個'Hello World!'的結果。

 

4.3 Accessing properties, arrays, lists, maps

這一步,將涉及好幾個部分的功能介紹,每一步,只寫相關的代碼,每一個特性的驗證處理,代碼里面已經分隔開了。

public class AccessingPropertiesApplication {
    static Logger logger = Logger.getLogger(AccessingPropertiesApplication.class);
    public static void main(String []args) {
        ExpressionParser parser = new SpelExpressionParser();
        //a. 調用String這個javaBean的'getBytes()'
        Expression exp = parser.parseExpression("'Hello World'.bytes");
        byte[] bytes = (byte[]) exp.getValue();
        logger.info("字節內容:"+ bytes);

        //b.嵌套的bean實例方法調用,通過'.'運算符
        exp = parser.parseExpression("'Hello World'.bytes.length");
        int len = (Integer) exp.getValue();
        logger.info("字節長度:" + len);

        //c. property訪問
        GregorianCalendar c = new GregorianCalendar();
        c.set(1856, 7, 9);
        //Inventor的構造函數參數分別是:name, birthday, and nationality.
        Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");
        parser = new SpelExpressionParser();
        exp = parser.parseExpression("name");
        EvaluationContext context = new StandardEvaluationContext(tesla);
        String name = (String) exp.getValue(context);
        logger.info("Inventor: " + name);

        //對對象實例的成員進行操作。 evals to 1856, 注意紀年中,起點是從1900開始。
        int year = (Integer) parser.parseExpression("Birthdate.Year  + 1900").getValue(context);
        //Inventor tesla設置出生地(瞎寫的信息)。
        tesla.setPlaceOfBirth(new PlaceOfBirth("America city", "America"));
        String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
        logger.info("year: " + year + ", city: " + city);

        //d. array, list操作
        // 先測試驗證array
        tesla.setInventions(new String []{"交流點","交流電發電機","交流電變壓器","變形記里面的縮小器"});
        EvaluationContext teslaContext = new StandardEvaluationContext(tesla);
        String invention = parser.parseExpression("inventions[3]").getValue(teslaContext, String.class);
        logger.info("Array: " + invention);
        //list測試驗證
        Society society = new Society();
        society.addMember(tesla);
        StandardEvaluationContext societyContext = new StandardEvaluationContext(society);
        // evaluates to "Nikola Tesla"
        String mName = parser.parseExpression("Members[0].Name").getValue(societyContext, String.class);
        // List and Array navigation
        // evaluates to "Wireless communication"
        String mInvention = parser.parseExpression("Members[0].Inventions[2]").getValue(societyContext, String.class);
        logger.info("List: mName= " + mName + ", mInvention= " + mInvention);

        //e. Map的操作
        //首先構建數據環境
        GregorianCalendar cm = new GregorianCalendar();
        cm.set(1806, 7, 9);
        Inventor idv = new Inventor("Idovr", cm.getTime(), "China,haha");
        Society soc = new Society();
        idv.setPlaceOfBirth(new PlaceOfBirth("Wuhan","China"));
        soc.addOfficer(Advisors, idv);
        soc.addOfficer(President, tesla);
        EvaluationContext socCtxt = new StandardEvaluationContext(soc);
        Inventor pupin = parser.parseExpression("Officers['president']").getValue(socCtxt, Inventor.class);
        String mCity = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(socCtxt, String.class);
        logger.info("Map case 1: " + mCity);
        // setting values
        Expression mExp = parser.parseExpression("Officers['advisors'].PlaceOfBirth.Country");
        mExp.setValue(socCtxt, "Croatia");
        //下面注釋掉的,是官方的做法,這個是有問題的,基於我的研究環境
        //parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(socCtxt, "Croatia");
        //String country = parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").getValue(socCtxt, String.class);
        String country = mExp.getValue(socCtxt, String.class);
        logger.info("Map case 2: " + country);
    }
}

注意:

1. SpEL對表達式中的property的首字母不區分大小寫,這個很重要。

2. 這里的操作,從另一個方面說明,SpEL操作,可以訪問實例的成員,成員的成員等嵌套的訪問,成員既可以是基礎成員,也可以是bean。如此看來,獲取實例的參數的值,不再是僅僅依賴反射可以實現,SpEL的操作,也一樣可以解決某些場景的問題。

3. map的操作,和基本的操作一樣,通過鍵的內容作為key獲取對應的value。整個邏輯,同樣是支持嵌套的操作。

 

4.4 Inline lists

list可以直接在一個表達式里面寫出來,借助於{}括號,相當於創建list實例的方式多了一種,不僅僅只有new的這一套,通過SpEL也可以完成這個功能。

@SpringBootApplication
public class InlineListApplication {
    static Logger logger = Logger.getLogger(InlineListApplication.class);
    public static void main(String []args) {
        ExpressionParser parser = new SpelExpressionParser();
        List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue();
        logger.info("單獨列表:" + numbers.toString());
        List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue();
        logger.info("二維列表:" + listOfLists.toString());
    }
}

輸出結果是這樣的:
2018-07-16 17:39:09,035 INFO => com.roomdis.rurale.InlineListApplication.main(InlineListApplication.java:24): 單獨列表:[1, 2, 3, 4]
2018-07-16 17:39:09,037 INFO => com.roomdis.rurale.InlineListApplication.main(InlineListApplication.java:26): 二維列表:[[a, b], [x, y]]

 

4.5 Array construction

通常情況下的數組定義,都是new出來的,這里,類似上面的inline lists,也可以通過SpEL來創建,空數組或者有初始值的都可以做到,一維或者多維的也不是問題。

@SpringBootApplication
public class ArrayConstructionApplication {
    static Logger logger = Logger.getLogger(ArrayConstructionApplication.class);
    public static void main(String []args) {
        ExpressionParser parser = new SpelExpressionParser();
        int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue();
        for (int it: numbers1) {
            logger.info("numbers1 element: " + it);
        }
        // Array with initializer
        int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue();
        for (int it: numbers2) {
            logger.info("numbers2 element: " + it);
        }
        // Multi dimensional array
        int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue();
        for(int it[]: numbers3) {
            logger.info("numbers3 dim 2 size: " + it.length);
        }
    }
}

輸出結果如下:
2018-07-16 17:54:10,681 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:18): numbers1 element: 0
2018-07-16 17:54:10,682 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:18): numbers1 element: 0
2018-07-16 17:54:10,682 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:18): numbers1 element: 0
2018-07-16 17:54:10,682 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:18): numbers1 element: 0
2018-07-16 17:54:10,683 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:23): numbers2 element: 1
2018-07-16 17:54:10,683 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:23): numbers2 element: 2
2018-07-16 17:54:10,683 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:23): numbers2 element: 3
2018-07-16 17:54:10,684 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:28): numbers3 dim 2 size: 5
2018-07-16 17:54:10,684 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:28): numbers3 dim 2 size: 5
2018-07-16 17:54:10,684 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:28): numbers3 dim 2 size: 5
2018-07-16 17:54:10,684 INFO => com.roomdis.rurale.ArrayConstructionApplication.main(ArrayConstructionApplication.java:28): numbers3 dim 2 size: 5

注意:目前不支持給多維數組在創建的時候初始化。

 

4.6 Relational operators

常見的關系運算,包含==,!=,<,>,<=,>=

public class RelationalOperatorApplication {
    static Logger logger = Logger.getLogger(RelationalOperatorApplication.class);
    public static void main(String []args) {
        ExpressionParser parser = new SpelExpressionParser();
        // evaluates to true
        boolean trueValue1 = parser.parseExpression("2 == 2").getValue(Boolean.class);
        logger.info("2 == 2: " + trueValue1);
        // evaluates to false
        boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
        logger.info("2 < -5.0: " + falseValue);
        // evaluates to true
        boolean trueValue2 = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);
        logger.info("'black' < 'block': " + trueValue2);
        //任何數都比null大
        boolean nullTrue = parser.parseExpression("0 > null").getValue(Boolean.class);
        logger.info("0 > null: " + nullTrue);        
        boolean nullFalse = parser.parseExpression("-1 < null").getValue(Boolean.class);
        logger.info("-1 < null: " + nullFalse);        
    }
}

輸出結果:
2018-07-16 19:16:28,936 INFO => com.roomdis.rurale.RelationalOperatorApplication.main(RelationalOperatorApplication.java:18): 2 == 2: true
2018-07-16 19:16:28,939 INFO => com.roomdis.rurale.RelationalOperatorApplication.main(RelationalOperatorApplication.java:21): 2 < -5.0: false
2018-07-16 19:16:28,939 INFO => com.roomdis.rurale.RelationalOperatorApplication.main(RelationalOperatorApplication.java:24): 'black' < 'block': true
2018-07-16 19:16:28,940 INFO => com.roomdis.rurale.RelationalOperatorApplication.main(RelationalOperatorApplication.java:27): 0 > null: true
2018-07-16 19:16:28,940 INFO => com.roomdis.rurale.RelationalOperatorApplication.main(RelationalOperatorApplication.java:30): -1 < null: false

注意:大於/小於的比較,當遇到和null比較時,遵循一個很簡單的規則:null被當做什么都沒有(不是當做0喲)。因此,任何值都是比null大的(x > null永遠都是true). 與此同時,沒有什么值會比什么都沒有小(x < null總是false)

 

除了常規的比較運算符操作外,SpEL還支持正則匹配,主要是matches操作符。

// evaluates to false
boolean instanceofValue = parser.parseExpression("'xyz' instanceof T(int)").getValue(Boolean.class);
logger.info("'xyz' instanceof T(int): " + instanceofValue);
// evaluates to true
boolean matchTrueValue =  parser.parseExpression("'5.00' matches '^\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
logger.info("'5.00' matches '^\\d+(\\.\\d{2})?$': " + matchTrueValue);
//evaluates to false
boolean matchFalseValue = parser.parseExpression("'5.0067' matches '^\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
logger.info("'5.0067' matches '^\\d+(\\.\\d{2})?$': " + matchFalseValue);

注意:每一種符號化的運算符,都有一個對應的字母符形式的符號,主要是考慮到在某些場合可能出現轉義(XML文檔),對應關系如下(不區分大小寫):
lt ('<'), gt ('>'), le ('<='), ge ('>='), eq ('=='), ne ('!='), div ('/'), mod ('%'), not ('!').

 

SpEL還支持邏輯運算以及算術運算,下面這里簡單例舉兩個例子:

//邏輯運算,支持and,or,not以及對應的組合
// -- AND --  evaluates to false
boolean andFalseValue = parser.parseExpression("true and false").getValue(Boolean.class);
logger.info("true and false: " + andFalseValue);
// -- NOT -- evaluates to false
boolean notFalseValue = parser.parseExpression("!true").getValue(Boolean.class);
logger.info("!true: " + notFalseValue);
//算術運算,支持 +, -, *, /, mod
// Addition
int two = parser.parseExpression("1 + 1").getValue(Integer.class);
logger.info("1 + 1: " + two);
String testString = parser.parseExpression("'test' + ' ' + 'string'").getValue(String.class);
logger.info("'test' + ' ' + 'string': " + testString);
// Subtraction
int four =  parser.parseExpression("1 - -3").getValue(Integer.class);
logger.info("1 - -3: " + four);

 

4.7 Assignment

給一個javaBean對象賦值,通常都是采用setValue方法,當然,也可以通過getValue的方式實現同樣的功能。

@SpringBootApplication
public class AssignmentApplication {
    static Logger logger = Logger.getLogger(AssignmentApplication.class);
    public static void main(String []args) {
        Inventor inventor = new Inventor();
        ExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor);
        parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2");
        logger.info("assignment <> inventor'name is: " + inventor.getName());
        // alternatively
        String aleks = parser.parseExpression("Name = 'Alexandar Seovic'").getValue(inventorContext, String.class);
        logger.info("assignment <> inventor'name is: " + aleks);
    }
}

 

4.8 Class Expression

特殊的T運算符可以用來指定一個java.lang.Class類的實例,即Class的實例。另外,靜態方法,也可以用T運算符指定。 T()可以用來指定java.lang包下面的類型,不必要全路徑指明,但是,其他路徑下的類型,必須全路徑指明。

@SpringBootApplication
public class TypesApplication {
    static Logger logger = Logger.getLogger(TypesApplication.class);
    public static void main(String []args) {
        ExpressionParser parser = new SpelExpressionParser();
        //非java.lang包下面的Class要指明全路徑
        Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
        //String是java.lang包路徑下的類,所以可以不用指明全路徑
        Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
        //T用來指定靜態方法
        boolean trueValue = parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR").getValue(Boolean.class);
        logger.info("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR :" + trueValue);
    }
}

注意:T(),這個運算符就告訴SpEL將運算符內的字符串當成是“類”處理,避免SpEL進行其他處理,尤其在調用某個類的靜態方法時。

 

4.9 Constructors

構造器函數能夠被new運算符調用,全路徑類名需要指定,除了基礎類型以及String類型。

@SpringBootApplication
public class ConstructorApplication {
    static Logger logger = Logger.getLogger(ConstructorApplication.class);
    public static void main(String []args){
        ExpressionParser p = new SpelExpressionParser();
        Inventor einstein = p.parseExpression("new com.roomdis.rurale.bean.Inventor('Albert Einstein','German')").getValue(Inventor.class);
        logger.info("constructor <> inventor name and nation: " + einstein.getName() + ", " + einstein.getNationality());
    }
}

 

4.10 Variables

變量在表達式中是可以被引用到的,通過#variableName。變量賦值是通過setValue在StandandExpressionContext中。

@SpringBootApplication
public class VariableApplication {
    static Logger logger = Logger.getLogger(VariableApplication.class);
    public static void main(String []args) {
        ExpressionParser parser = new SpelExpressionParser();
        Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
        StandardEvaluationContext context = new StandardEvaluationContext(tesla);
        //在StandardEvaluationContext這個上下文環境中定義一個新的變量newName,並給他賦值Mike Tesla
        context.setVariable("newName", "Mike Tesla");
        //通過getValue的方式,給javaBean賦值。
        parser.parseExpression("Name = #newName").getValue(context);
        logger.info(tesla.getName());

        //#this 和 #root的介紹
        // create an array of integers
        List<Integer> primes = new ArrayList<Integer>();
        primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
        StandardEvaluationContext context_sharp = new StandardEvaluationContext();
        context_sharp.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_sharp);
    }
}

注意:SpEL中獲取變量的值,是通過#variableName操作的,這個變量可以是任何類型,既可以是基礎類型,也可以是bean實例。
#this表示當前操作的數據對象,#root表示SpEL表達式上下文的根節點對象,這里要說明的概念就是上下文環境,即StandardEvaluationContext定義的環境,雖然他不是SpEL必須定義的,但是SpEL默認是將ApplicationContext定義為根節點對象,即默認#root的值為ApplicationContext。一般的,給StandardEvaluationContext指定一個對象,例如本例中前半部分,將tesla作為StandardEvaluationContext的構造函數入參,即此時的#root為telsa。

 

4.11 Ternary Operator (If-Then-Else)

就是一般的正常使用的三目運算符。三目運算有一個特殊的運算,即Elvis Operator。
String falseString = parser.parseExpression("false ? 'trueExp' : 'falseExp'").getValue(String.class);這個是正常的三目運算。
針對三目運算符,在SpEL里面,針對下面的場景,對其做了一種簡化,即當變量不等null時取變量當前值的場景。
String name = "Elvis Presley";
String displayName = name != null ? name : "Unknown";
簡寫成String displayName = name?: "Unknown";
完整的驗證案例程序:

public class TernaryApplication {
    static Logger logger = Logger.getLogger(TernaryApplication.class);
    public static void main(String []args) {
        ExpressionParser parser = new SpelExpressionParser();
        Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
        StandardEvaluationContext context = new StandardEvaluationContext(tesla);
        //在StandardEvaluationContext這個上下文環境中定義一個新的變量newName,並給他賦值Mike Tesla
        context.setVariable("newName", "Mike Tesla");
        //通過getValue的方式,給javaBean賦值。
        parser.parseExpression("Name = #newName").getValue(context);
        String expression = "name == 'Mike Tesla'?'修改實例成員變量name成功.':'修改實例成員變量name失敗.'";
        String name = tesla.getName();
        logger.info("new name: " + name);
        String result = parser.parseExpression(expression).getValue(context, String.class);
        logger.info(result);

        //下面是驗證elivs運算符的案例
        name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
        logger.info(name); // Nikola Tesla
        tesla.setName(null);
        name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);
        logger.info(name); // Elvis Presley
    }
}

 

4.12 Safe Navigation operator

安全導航運算符,主要用來避免實例操作出現NullPointerException。典型的是,當你訪問一個對象的實例,你首先要確保這個實例不是null,才有可能去訪問其方法或者屬性。SpEL通過Safe Navigation運算符可以避免這種因為是null拋出異常,取而代之的是用null返回值。

public class SafeNavigationApplication {
    static Logger logger = Logger.getLogger(SafeNavigationApplication.class);
    public static void main(String []args) {
        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);
        logger.info("before <> city: " + city); // Smiljan
        tesla.setPlaceOfBirth(null);
        city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
        logger.info("after <> city: " + city); // null - does not throw NullPointerException!!!
    }
}

 

4.13 Collection Selection

集合選擇是一個非常有用的表達式語言特性,它方便你實現從一個集合選擇符合條件的元素放入另外一個集合中。語法是:?[selectionExpression]
集合選擇功能既可以用在list列表,也可以用於map結構。

public class CollectionApplication {
    static Logger logger = Logger.getLogger(CollectionApplication.class);
    public static void main(String []args) {
        //map中指定key的查找
        ExpressionParser parser = new SpelExpressionParser();
        Inventor in1 = new Inventor("zhangsan", "China");
        Inventor in2 = new Inventor("wangwu", "America");
        Inventor in3 = new Inventor("tesla", "Serbian");
        Society society = new Society();
        society.addMember(in1);
        society.addMember(in2);
        society.addMember(in3);
        EvaluationContext societyContext = new StandardEvaluationContext(society);
        List<Inventor> list = (List<Inventor>) parser.parseExpression("Members.?[Nationality == 'Serbian']").getValue(societyContext);
        logger.info("Inventor list count: " + list.size());

        //map中基於value的條件進行查找
        Map<String, Integer> map =  new HashMap<String, Integer>();
        map.put("zhangsan", 31);
        map.put("lishi", 23);
        map.put("wangwu", 15);
        map.put("zhaoliu", 29);
        society.addGust(map);
        societyContext = new StandardEvaluationContext(society);
        Map<String, Integer> newMap = (Map<String, Integer>)parser.parseExpression("mguest.?[value<27]").getValue(societyContext);
        for(String ky : newMap.keySet()){
            logger.info("name: " + ky + ", value: " + newMap.get(ky));
        }

        //驗證 ^[selectionExpression]取第一個滿足條件的,$[selectionExpression]取最后一個滿足條件的
        Map<String, Integer>  v1 = (HashMap) parser.parseExpression("mguest.^[value<27]").getValue(societyContext);
        logger.info("first element: " + v1.toString());
        Map<String, Integer> v2 = (HashMap) parser.parseExpression("mguest.$[value<27]").getValue(societyContext);
        logger.info("last element: " + v2.toString());
    }
}

這個時候,若Society這個類種的mguest成員是非public的話,會遇到下面的錯誤:

Exception in thread "main" org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'mguest' cannot be found on object of type 'com.roomdis.rurale.bean.Society' - maybe not public?
    at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:224)
    at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:94)
    at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:81)
    at org.springframework.expression.spel.ast.CompoundExpression.getValueRef(CompoundExpression.java:51)
    at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:87)
    at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:120)
    at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:242)
    at com.roomdis.rurale.CollectionApplication.main(CollectionApplication.java:45)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

可以通過^[selectionExpression]獲取第一個成員,通過$[selectionExpression]取最后一個元素。具體例子,上面的案例程序中最后一部分就是這個內容。

 

4.14 Collection Projection

集合投射就是在一個集合的基礎上基於一定的規則抽取出相應的數據,重新生成一個集合。表達式![projectionExpression]

public class CollectionProjectionApplication {
    static Logger logger = Logger.getLogger(CollectionProjectionApplication.class);
    public static void main(String []args) {
        ExpressionParser parser = new SpelExpressionParser();
        Inventor iv1 = new Inventor("Tesla","Serbian");
        iv1.setPlaceOfBirth(new PlaceOfBirth("Buzhidao", "Serbian"));
        Inventor iv2 = new Inventor("Mayun","China");
        iv2.setPlaceOfBirth(new PlaceOfBirth("Hangzhou", "China"));
        Inventor iv3 = new Inventor("Sunzhengyi", "Japan");
        iv3.setPlaceOfBirth(new PlaceOfBirth("Tokyo", "Japan"));
        Society society = new Society();
        society.addMember(iv1);
        society.addMember(iv2);
        society.addMember(iv3);
        EvaluationContext context = new StandardEvaluationContext(society);
        List<String> placesOfBirth = (List<String>)parser.parseExpression("Members.![placeOfBirth.city]").getValue(context);
        for (String pob: placesOfBirth){
            logger.info("P O B: " + pob);
        }
    }
}

集合投射,對map也可以適用,只是獲取后的結果是一個list,里面的成員是map.entry.

 

4.15 Expression templating

表達式模板允許用戶將字符串和一個或者多個EL表達式連接起來。每個EL表達式用一個#{}括起來。

public class ExpressionTemplateApplication {
    static Logger logger = Logger.getLogger(ExpressionTemplateApplication.class);
    public static void main(String []args) {
        ExpressionParser parser = new SpelExpressionParser();
        String randomPhrase = parser.parseExpression("random number is #{T(java.lang.Math).random()}", new TemplateParserContext()).getValue(String.class);
        logger.info("Expression Template: " + randomPhrase);
    }
}

class TemplateParserContext implements ParserContext {

    /*
     *啟用表達式模板功能,要繼承ParserContext接口,實現其中的三個基本方法,分別是告知解析器這個表達式是否啟用模板,模板的起始和結束符。
     */

    @Override
    public boolean isTemplate() {
        return true;
    }

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

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

 

SpEL的功能很大,

 


免責聲明!

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



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