zoukankan      html  css  js  c++  java
  • Spring 4 官方文档学习(五)核心技术之SpEL

    题外话

    官方文档用evaluate这个单词来描述从表达式中获得实际内容的过程。如果直译的话,应该是评估、估值之类的意思。个人以为翻译成解析更易懂,但parse已经是解析了,为了避免冲突,就只好保留了evaluate的形式。--如果有更好的建议,请务必通知我。


    1、介绍

    SpEL支持在运行时 查询操作对象图。

    2、功能概览

    英文 中文
    Literal expressions 字面值表达式
    Boolean and relational operators 布尔和关系操作符
    Regular expressions 正则表达式
    Class expressions 类表达式
    Accessing properties, arrays, lists, maps 访问properties、arrays、lists、maps
    Method invocation 方法调用
    Relational operators 关系操作符
    Assignment 赋值
    Calling constructors 调用构造器
    Bean references bean引用
    Array construction 构建数组
    Inline lists 内联lists
    Inline maps 内联maps
    Ternary operator 三元操作符
    Variables 变量
    User defined functions 用户定义的功能
    Collection projection 集合投影
    Collection selection 集合选择
    Templated expressions 模板化表达式

    3、使用SpEL的API来evaluate 表达式

    前提,搭建一个小环境,将Spring的基本jar包放进去。 -- 最简单的办法,使用STS新建一个Spring Boot项目即可。

    然后,使用JUnit测试下面的代码:

    package cn.larry.spel;
    
    import org.junit.Test;
    import org.springframework.expression.Expression;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    
    public class TheTest {
        @Test
        public void run() {
            ExpressionParser parser=new SpelExpressionParser();
            String str="'Hello World'";
            
            Expression exp = parser.parseExpression(str);
    //        Object value = exp.getValue();
            String value = exp.getValue(String.class);
            
            System.out.println(value);
        }
    }

    注意:仅限于不需要Spring容器的测试,就是仅限于直接调用API的测试。

    你最有可能用到的SpEL相关的类和接口位于 org.springframework.expression 包及其子包,以及 spel.support 包中。

    ExpressionParser接口,负责解析(parse)表达式字符串。

    Expression接口,负责evaluate前面定义的表达式字符串 -- 是指ExpressionParser解析出来的吗?

    再来看看SpEL如何调用方法

    // demo for calling method
    @Test
    public void run1() {
        ExpressionParser parser = new SpelExpressionParser();
        String str = "'Hello World'.concat('!!!')";
    
        Expression exp = parser.parseExpression(str);
        Object value = exp.getValue();
    
        System.out.println(value);
    }

    还有如何获取property -- 前提是JavaBean:

    // demo for accessing property of JavaBean
    @Test
    public void run2() {
        ExpressionParser parser = new SpelExpressionParser();
        String str = "'Hello World'.bytes";
    
        Expression exp = parser.parseExpression(str);
        byte[] value = exp.getValue(byte[].class);
    
        System.out.println(value);
    }

    还支持获取nested property -- 前提是JavaBean:

    // demo for accessing nested property of JavaBean
    @Test
    public void run3() {
        ExpressionParser parser = new SpelExpressionParser();
        String str = "'Hello World'.bytes.length";
    
        Expression exp = parser.parseExpression(str);
        int value = exp.getValue(int.class);
    
        System.out.println(value);
    }

    调用构造器

    //demo for calling constructor
    @Test
    public void run4() {
        ExpressionParser parser = new SpelExpressionParser();
        String str = "new String('Hello World').toUpperCase()";
    
        Expression exp = parser.parseExpression(str);
        Object value = exp.getValue();
    
        System.out.println(value);
    }

    上面的用法是直接解析一个expression string,但更常见的用法是根据表达式从特定对象中获取内容

    "The more common usage of SpEL is to provide an expression string that is evaluated against a specific object instance (called the root object)."

    有两种用法,具体选择哪种取决于你每次evaluate the expression时对象是否会发生改变。来看看两种代码:

    /**
     * The StandardEvaluationContext is relatively expensive to construct and during repeated usage 
     * it builds up cached state that enables subsequent expression evaluations to be performed more quickly. 
     * For this reason it is better to cache and reuse them where possible, rather than construct a new one for each expression evaluation.
     */
    @Test
    public void run5() {
        GregorianCalendar cal = new GregorianCalendar();
        cal.set(1856, 7, 9);
    
        Inventor inventor = new Inventor("Nicolas Tesla", cal.getTime(), "serbian");
    
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression("name");
    
        // 利用目标对象(root object)构造一个context。开销相对较高,建议仅用于不常改变的对象。
        EvaluationContext evaluationContext = new StandardEvaluationContext(inventor);
        
        inventor.setName("Newton"); // 还是会起作用
        
        // 从指定的context中获取本表达式对应的值。
        String value = (String) expression.getValue(evaluationContext);
        System.out.println(value);
    }
    
    // 如果root object可能发生改变,不建议使用其创建一个context,因为开销较高。应该如下:
    @Test
    public void run6() {
        GregorianCalendar cal = new GregorianCalendar();
        cal.set(1856, 7, 9);
    
        Inventor inventor = new Inventor("Nicolas Tesla--", cal.getTime(), "serbian");
    
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression("name");
    
        // 直接从目标对象中获取本表达式对应的值。内部还是会创建一个context。
        // the expression evaluation infrastructure creates and manages a default evaluation context internally
        String value = (String) expression.getValue(inventor);  
        System.out.println(value);
        
        inventor.setName("Newton"); // 也起作用了
    
        value = (String) expression.getValue(inventor);  
        System.out.println(value);
    }

    这里的关键在于:Expression是从指定的上下文中获取相应的内容。粗略的说,Expression指定了要获取的property name,但还需要指定从什么地方获取该property value -- 是从EvaluationContext中获取的。

    上面两个案例,run5() 是用目标对象(inventor)直接构造了一个StandardEvaluationContext,以供Expression从中获取值; run6() 则是让Expression直接从目标对象(inventor)中获取值 --但实际上内部还是构造了一个EvaluationContext。

    二者的不同之处在于StandardEvaluationContext开销较大。

    The StandardEvaluationContext is relatively expensive to construct and during repeated usage, it builds up cached state that enables subsequent expression evaluations to be performed more quickly. For this reason it is better to cache and reuse them where possible, rather than construct a new one for each expression evaluation.

    In standalone usage of SpEL there is a need to create the parser, parse expressions and perhaps provide evaluation contexts and a root context object. However, more common usage is to provide only the SpEL expression string as part of a configuration file, for example for Spring bean or Spring Web Flow definitions. In this case, the parser, evaluation context, root object and any predefined variables are all set up implicitly, requiring the user to specify nothing other than the expressions.

    3.1、EvaluationContext 接口

    当evaluate an expression以获取properties、methods、fields以及帮助执行类型转换时,使用该接口。现成的实现类是 StandardEvaluationContext,利用了反射来操作对象、缓存 java.lang.reflect.Method、java.lang.reflect.Field和java.lang.reflect.Constructor的实例以增进性能。

    你可以:

    指定目标对象(root object):使用无参构造,再调用setRootObject();或者,使用有参构造。

    也可以指定Expression中使用的变量和方法:setVariable()、registerFunction()。

    还可以register custom ConstructorResolvers, MethodResolvers, and PropertyAccessors to extend how SpEL evaluates expressions。

    详见JavaDoc。

    类型转换

    默认情况下,SpEL使用Spring core中的conversion service。支持泛型。

    示例代码:

    // SpEL默认使用Spring core的conversion service。默认支持泛型。
    @Test
    public void run8() {
        class Simple {
            public List<Boolean> booleanList = new ArrayList<Boolean>();
        }
    
        Simple simple = new Simple();
        simple.booleanList.add(true);
    
        StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple);
    
        SpelExpressionParser parser = new SpelExpressionParser();
        // false is passed in here as a string. SpEL and the conversion service will
        // correctly recognize that it needs to be a Boolean and convert it
        parser.parseExpression("booleanList[0]").setValue(simpleContext, "false");// 不止能获取,还能设置。
    
        // b will be false
        Boolean b = simple.booleanList.get(0);
        
        System.out.println(b);
    }

    3.2、Parser configuration

    通过设置Parser,可以改变某些默认的行为。典型的,例如 通过索引访问数组或集合,如果返回的是null,可以让它自动创建元素(empty element)。

    例如,当你越界访问数组或list的元素时,可以让数组或list的长度自动增长!!!

    代码如下:

    // demo for parser configuration
    @Test
    public void run9() {
        class Demo {
            public List<String> list;
        }
    
        // Turn on:
        // - auto null reference initialization
        // - auto collection growing
        SpelParserConfiguration config = new SpelParserConfiguration(true, true); // 关键,见JavaDoc
    
        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
        System.out.println(demo.list);
    }

    3.3、SpEL compilation (略)

    http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#expressions-spel-compilation

    请注意,上面的测试代码都是直接调用的API,所以不必启动环境(没有容器)。但下面的不同,多数需要启动环境(或者模拟环境RunWith)。


    4、支持定义bean definition的表达式

    SpEL可以用在基于XML或基于注解的配置元数据以定义BeanDefinition。语法: #{ <expression string> }

    4.1、基于XML的配置

    使用expression设置property或者constructor-args:

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

    上面的 T 表示类型,后面会讲到。

    另外,系统已经预定义了 systemProperties,所以可以直接引用(官方文档用的是user.region,实际上是不存在的):

    <bean id="person" class="cn.larry.demo.Person">
        <property name="name" value="#{ systemProperties['user.name'] }"/>
    
        <!-- other properties -->
    </bean>

    还可以引用其他bean:

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

    4.2、基于注解的配置

    @Value 注解可以放在fields、methods、method/constructor的参数上,以注入默认值。

    示例代码:

    package cn.larry.demo;
    
    import java.util.Set;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    /**
     * 这里才是创建了一个容器。
     * 
     * @author Larry
     *
     */
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class LarrySpringDemoSpelApplicationTests {
    
        @Test
        public void contextLoads() {
        }
    
        @Value("#{ systemProperties['user.country'] }")
        // @Value("#{ systemProperties['java.vm.version'] }")
        private String region;
    
        @Test
        public void run1() {
            System.out.println(region);
            Set<Object> keySet = System.getProperties().keySet();
            System.out.println(keySet);
        }
    }

    5、Language Reference

    5.1、字面值表达式 (literal expression)

    字面值表达式支持的类型包括strings、dates、numeric values(int、real、hex)、boolean 以及 null。

    字符串使用单引号间隔。如要输出单引号,两个单引号即可。(转义)

    简单的示例如下(不需要容器),实际工作环境中往往是复合的复杂用法。

    @Test
    public void run11() {
        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();
    
        System.out.println(helloWorld);
        System.out.println(avogadrosNumber);
        System.out.println(maxValue);
        System.out.println(trueValue);
        System.out.println(nullValue);
    }

    数字支持:负数、指数、十进制小数。但默认real number是通过Double.parseDouble()解析的。

    5.2、Properties、Arrays、Lists、Maps、Indexers

    导航property ref很简单,例如:

    // demo for property ref
    @Test
    public void run12() {
        GregorianCalendar cal = new GregorianCalendar();
        cal.set(1856, 7, 9);
    
        Inventor inventor = new Inventor("Nicolas Tesla", cal.getTime(), "serbian");
    
        SpelExpressionParser parser = new SpelExpressionParser();
    
        // 利用目标对象(root object)构造一个context。开销相对较高,建议仅用于不常改变的对象。
        EvaluationContext context = new StandardEvaluationContext(inventor);
    
        int year = (Integer) parser.parseExpression("Birthday.Year + 1900").getValue(context);
        System.out.println(year);
    }

    注意:property name的首字母大小写可以忽略。数组和list以及map的内容都是通过[]获取的。不再演示。

    5.3、inline lists 内联lists

    lists可以通过 {} 直接填写内容,如无内容,代表empty list。如下:

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

    嗯嗯嗯?For performance reasons, if the list is itself entirely composed of fixed literals then a constant list is created to represent the expression, rather than building a new list on each evaluation. -- 这意思得是说这种情况下每次evaluate用的其实都是同一个List?

    5.4、inline maps 内联maps

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

    类似inline lists,可以直接写在 {} 中,且 {:} 代表empty map。

    inline maps特别之处在于,key可以不用单引号!--也可以用。

    For performance reasons, if the map is itself composed of fixed literals or other nested constant structures (lists or maps) then a constant map is created to represent the expression, rather than building a new map on each evaluation. -- 类似inline maps,不再解释。

    5.5、Array construction  构建数组

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

    5.6、Methods

    与正常的编码式调用方法一样,且也支持变量参数!

    // string literal, evaluates to "bc"
    String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);
    
    // evaluates to true
    boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
            societyContext, Boolean.class);

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

    务必小心primitive类型,在SpEL中它们会立即变成包装类型!所以, 1 instanceOf T(int) evaluates to false!而 1 instanceOf T(Integer) evaluates to true!!!

    同jstl一样,==、!=、<、<=、>、>=也可以用eq、ne、lt、le、gt、ge表示。此外还可以:div (/), mod (%), not (!)。

    逻辑操作符 Logical operators

    and、or、not

    // -- AND --
    
    // evaluates to false
    boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
    
    // evaluates to true
    String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
    boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
    
    // -- OR --
    
    // evaluates to true
    boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
    
    // evaluates to true
    String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
    boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
    
    // -- NOT --
    
    // evaluates to false
    boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
    
    // -- AND and NOT --
    String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
    boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

    数学操作符 Mathematical operators

    +操作符可以用于numbers和strings。-*/只能用于numbers。

    另外还支持%和^。优先级同Java语法。

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

    5.8、Assignment 赋值

    可以通过expression的setValue进行赋值,也可以在getValue内部实现!代码如下:

    // demo for assignment
    @Test
    public void run13() {
        GregorianCalendar cal = new GregorianCalendar();
        cal.set(1856, 7, 9);
    
        Inventor inventor = new Inventor("Nicolas Tesla", cal.getTime(), "serbian");
    
        SpelExpressionParser parser = new SpelExpressionParser();
    
        // 利用目标对象(root object)构造一个context。开销相对较高,建议仅用于不常改变的对象。
        EvaluationContext context = new StandardEvaluationContext(inventor);
    
        parser.parseExpression("name").setValue(context, "Alexander"); // way 1
        parser.parseExpression("nationality = 'eng'").getValue(context); // way 2
    
        System.out.println(inventor);
    }

    5.9、Types 类型

    特殊的T操作符用于指定 java.lang下的类型。也可以通过该操作符调用静态方法。

    使用该操作符引用java.lang下的类型时,可以省略包路径。但其他引用不能省略!

    @Test
    public void run14() {
        SpelExpressionParser parser = new SpelExpressionParser();
        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);
        
        System.out.println(dateClass);
        System.out.println(stringClass);
        System.out.println(trueValue);
        
        Class inventorClass = parser.parseExpression("T(cn.larry.demo.domain.Inventor)").getValue(Class.class);
        System.out.println(inventorClass);
    }

    5.10、Constructors 构造器

    使用 new操作符 可以调用构造器。

    此时,除了primitive 类型和String类型之外,其他所有类型都必须是全路径

    // demo for constructor. note this, besides primitive types and String, all types should be full qualified name.
    @Test
    public void run15() {
        SpelExpressionParser parser = new SpelExpressionParser();
        
        Inventor inventor = parser.parseExpression("new cn.larry.demo.domain.Inventor('Larry',new java.util.Date(),'cn')").getValue(Inventor.class);
        System.out.println(inventor);
    }

    5.11、Variables 变量

    可以引用变量,格式:#variableName。变量是使用StandardEvaluationContext的setVariable来设置的。如下:

    // demo for variables. use setVariable method of StandardEvaluationContext to set variables.
    @Test
    public void run16() {
        Inventor tesla = new Inventor("Nikola Tesla", new Date(), "Serbian");
        StandardEvaluationContext context = new StandardEvaluationContext(tesla);
        context.setVariable("newName", "Mike Tesla"); // 这里,设置变量!
    
        SpelExpressionParser parser = new SpelExpressionParser();
        parser.parseExpression("Name = #newName").getValue(context);// 这里,通过 #newName 引用!
    
        System.out.println(tesla.getName()); // "Mike Tesla"
    }

    #this、#root

    The variable #this is always defined and refers to the current evaluation object (against which unqualified references are resolved). The variable #root is always defined and refers to the root context object. Although #this may vary as components of an expression are evaluated, #root always refers to the root.

    // demo for #this and #root
    @Test
    public void run17() {
        // create an array of integers
        List<Integer> primes = new ArrayList<Integer>();
        primes.addAll(Arrays.asList(2, 3, 5, 7, 11, 13, 17));
    
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("primes", primes); //set var
    
        ExpressionParser parser = new SpelExpressionParser();
        // all prime numbers > 10 from the list (using selection ?{...}) evaluates to [11, 13, 17]
        List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression("#primes.?[#this>10]") // ? what is that for
                .getValue(context);
        System.out.println(primesGreaterThanTen);
    }

    奇怪,这里的 #primes.?[#this>10],其中.?是什么意思? #this难道是当前的变量??? -- 待研究。

    5.12、Functions 功能(略)

    通过注册自己的功能来扩展SpEL!

    http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#expressions-ref-functions

    5.13、Bean references

    如果evaluation context配置了一个bean resolver,就可以使用@符号来查找beans!

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

    实际上是通过调用bean resolver的resolve(context, exp)来实现的!!!

    当需要访问的是一个factory 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);

    5.14、Ternary operator 三目运算符

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

    5.15、Elvis operator

    是三目运算符的简写,用于groovy语言中。

    通常,如果使用三目运算符,会重复一个变量两次,例如:

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

    Elvis操作符则简化了这一形式:

    ExpressionParser parser = new SpelExpressionParser();
    String name = parser.parseExpression("name?:'Unknown'").getValue(String.class);
    
    System.out.println(name); // 'Unknown'
    @Value("#{systemProperties['pop3.port'] ?: 25}")

    5.16、Safe Navigation operator  安全导航操作符

    用于避免空指针异常,来自Groovy语言。

    一般来说,当你引用一个对象时,在访问其property或method之前需要确认非null。而安全导航操作符则简单的返回一个null,而非抛出异常。

    String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);

    上面,这个?就是安全导航操作符? --用在对象后面!!!

    5.17、Collection Selection 集合选择

    选择是一个强大的表达式语言特色。

    格式: ?[selectionExpression]。会过滤一个集合,返回一个新集合。如下:

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

    上面的代码,会返回Members集合中Nationality是Serbian的Inventor。但是,搞不明白那个点的意思,Members是集合,集合还能用点?还是说???

    即可用于list也可以用于map。对list来说,是对每个元素进行选择;对map来说,则是对每个键值对Map.Entry进行选择。

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

    除了返回选择的元素之外,还可以直接返回第一个或最后一个:^[…]$[…]

    5.18、Collection Projection 集合投影

    格式:![projectionExpression]。

    功能不好描述,但代码简单易懂。假定我们有一个Inventor的List,现在要用Inventor的name List,代码如下:

    // demo for collection projection
    @Test
    public void run18() {
        Inventor i1 = new Inventor("ant", new Date(), "cn");
        Inventor i2 = new Inventor("egg", new Date(), "en");
        Inventor i3 = new Inventor("grass", new Date(), "us");
    
        List<Inventor> list = new ArrayList<Inventor>();
        list.add(i1);
        list.add(i2);
        list.add(i3);
    
        SpelExpressionParser parser = new SpelExpressionParser();
        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("list", list);
        
        @SuppressWarnings("unchecked")
        List<Inventor> value = (List<Inventor>) parser.parseExpression("#list.![name]").getValue(context);
        System.out.println(value);
    }

    也可以map,略。

    5.19、Expression templating 表达式模板化

    表达式模板允许将字符串字面值与一个或多个evaluation blocks混合起来。每个evaluation block的前后缀都可以自定义,但默认使用 #{}。 如下:

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

    关键是parseExpression()的第二个参数,是ParserContext类型。ParserContext接口用来影响表达式如何被解析以便支持模板功能。

    TemplateParserContext接口的源码如下:

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

    6、用到的类

    http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#expressions-example-classes

    上面这个地址是官方所用的类,与我的类肯定有出入--因为开始的时候没注意到官方还提供了类,所以逆推着写了自己的类,好在问题不大,就不再描述了。

    我的代码可以来码云下载: SpringSpEL_Demo

    ps:顺便输出了下SystemProperties的keySet:

    [java.runtime.name, sun.boot.library.path, java.vm.version, java.vm.vendor, java.vendor.url, path.separator, java.vm.name, file.encoding.pkg, user.country, user.script, sun.java.launcher, sun.os.patch.level, PID, java.vm.specification.name, user.dir, java.runtime.version, java.awt.graphicsenv, org.jboss.logging.provider, java.endorsed.dirs, os.arch, java.io.tmpdir, line.separator, java.vm.specification.vendor, user.variant, os.name, sun.jnu.encoding, spring.beaninfo.ignore, java.library.path, java.specification.name, java.class.version, sun.management.compiler, os.version, user.home, user.timezone, java.awt.printerjob, file.encoding, java.specification.version, java.class.path, user.name, java.vm.specification.version, sun.java.command, java.home, sun.arch.data.model, user.language, java.specification.vendor, awt.toolkit, java.vm.info, java.version, java.ext.dirs, sun.boot.class.path, java.awt.headless, java.vendor, file.separator, java.vendor.url.bug, sun.io.unicode.encoding, sun.cpu.endian, sun.desktop, sun.cpu.isalist]

    如果不知道Spring Boot,请先看这个:Spring Boot学习

    如果不知道STS,请先看这个:Spring Tools Suite (STS) 简介。--需要先了解Spring Boot。

    嗯,篇幅都很短,半个小时基本可以搞定,很简单的东西。

  • 相关阅读:
    二叉树遍历
    nginx反向代理signalr
    SignalR入坑笔记
    CSS一些特殊图形
    Powershell下git中文乱码
    使用VisualStudio直接运行简单的C#语句
    wpf实现一个windows定时关机的工具
    Asp.NetCoreWebApi
    MySql权限丢失问题解决
    Systemd 入门教程:命令篇
  • 原文地址:https://www.cnblogs.com/larryzeal/p/5964621.html
Copyright © 2011-2022 走看看