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的功能很大,

猜你喜欢

转载自www.cnblogs.com/shihuc/p/9338173.html