xiaobaoqiu Blog

Think More, Code Less

Spring SpEL语法

最近项目中遇到一个问题,无意中发现Spring SpEL语法,Spring的Cache正是基于此实现的,感觉很强大,学习一下。

1.简介

SpEL官网:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html

Spring表达式语言全称为Spring Expression Language(缩写为SpEL),能在运行时构建复杂表达式、存取对象图属性、对象方法调用等等,并且能与Spring功能完美整合,如能用来配置Bean定义。 表达式语言给静态Java语言增加了动态功能。

SpEL是单独模块,只依赖于core模块,不依赖于其他模块,可以单独使用。

SpEL支持以下功能:

1.文本表达式(Literal expressions)
2.布尔操作和关系操作(Boolean and relational operators)
3.正则表达式(Regular expressions)
4.类表达式(Class expressions)
5.访问属性,数组,list和map(Accessing properties, arrays, lists, maps)
6.方法调用(Method invocation)
7.关系操作符(Relational operators)
8.赋值(Assignment)
9.调用构造器(Calling constructors)
10.Bean引用(Bean references)
11.数组构造(Array construction)
12.内脸list(Inline lists)
13.内联Map(Inline maps)
14.三元运算符(Ternary operator)
15.变量(Variables)
16.自定义函数(User defined functions)
17.集合投影(Collection projection)
18.集合筛选(Collection selection)
19.模板表达式(Templated expressions)

2.SpEL使用

下面通过一些简单的case来示范SpEL的使用.

2.1 第一个SpEL

SpEL表达式可以直接使用ExpressionParser解析,主要是调用ExpressionParser的parseExpression()方法:

1
2
3
4
5
public static void test2_1(){
    Expression expression = parser.parseExpression("'Testing Spring Expression Framework'");
    String message = (String) expression.getValue();
    System.out.println("Message is " + message);
}

2.2 字面量表达式

SpEL支持的字面量包括字符串、数字类型(int、long、float、double)、bool类型、null类型,注意字符串必须要单引号括起来:

1
2
3
4
5
6
7
8
9
10
public static void test2_2(){
    // 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();
}

2.3 算数运算表达式

SpEL支持加(+)、减(-)、乘(*)、除(/)、求余(%)、幂(^)运算,SpEL还提供求余(MOD)和除(DIV)而外两个运算符,与"%“和”/“等价,不区分大小写:

1
2
3
4
5
public static void test2_3(){
    int result1 = parser.parseExpression("1+2-3*4/2").getValue(Integer.class);
    int result2 = parser.parseExpression("4%3").getValue(Integer.class);
    int result3 = parser.parseExpression("2^3").getValue(Integer.class);
}

2.4 关系表达式

等于(==)、不等于(!=)、大于(>)、大于等于(>=)、小于(<)、小于等于(<=),区间(between)运算:

1
2
3
4
public static void test2_4(){
    boolean result1 = parser.parseExpression("1>2").getValue(boolean.class);
    boolean result2 = parser.parseExpression("1 between {1, 2}").getValue(boolean.class);
}

between运算符右边操作数必须是列表类型,且只能包含2个元素。第一个元素为开始,第二个元素为结束,区间运算是包含边界值的。

SpEL同样提供了等价的"EQ" 、"NE"、 “GT"、"GE"、 "LT” 、"LE"来表示等于、不等于、大于、大于等于、小于、小于等于,不区分大小写。

2.5 逻辑表达式

且(and)、或(or)、非(!或NOT),注意,逻辑运算符不支持 Java中的 && 和 ||:

1
2
3
4
5
6
7
public static void test2_5() {
    String expression1 = "2>1 and (!true or !false)";
    boolean result1 = parser.parseExpression(expression1).getValue(boolean.class);

    String expression2 = "2>1 and (NOT true or NOT false)";
    boolean result2 = parser.parseExpression(expression2).getValue(boolean.class);
}

2.6 字符串连接及截取表达式

使用"+“进行字符串连接,使用”‘String’[0] [index]“来截取一个字符,目前只支持截取一个,如”‘Hello ’ + ‘World!’“得到"Hello World!";而”‘Hello World!’[0]“将返回"H"。

1
2
3
4
5
6
7
8
9
10
public static void test2_6() {
//    String expression1 = "'Hello Spring SpEL'[0] [5]";
//    String result1 = parser.parseExpression(expression1).getValue(String.class);
//    System.out.println("result1=" + result1);

    String expression2 = "'Hello' + 'Spring' + 'SpEL.'";
    String result2 = parser.parseExpression(expression2).getValue(String.class);
//    System.out.println("result1=" + result1);
    System.out.println("result2=" + result2);
}

2.7 三目运算

三目运算符 “表达式1?表达式2:表达式3"用于构造三目运算表达式,如"2>1?true:false"将返回true;

1
2
3
4
public static void test2_7() {
    boolean result1 = parser.parseExpression("2>1?true:false").getValue(boolean.class);
    System.out.println("result1=" + result1);
}

2.8 正则表达式

可以正则,格式为:str matches regex

1
2
3
4
public static void test2_8() {
    boolean result1 = parser.parseExpression("'123' matches '\\d{3}'").getValue(boolean.class);
    System.out.println("result1=" + result1);
}

2.9 括号优先级表达式

使用"(表达式)“构造,括号里的具有高优先级

1
2
3
4
5
6
public static void test2_9() {
    int result1 = parser.parseExpression("1+2-3*4/2").getValue(Integer.class);
    int result2 = parser.parseExpression("1+(2-3)*4/2").getValue(Integer.class);
    System.out.println("result1=" + result1);
    System.out.println("result2=" + result2);
}

2.10 类类型表达式

使用"T(Type)“来表示java.lang.Class实例,"Type"必须是类全限定名,"java.lang"包除外,即该包下的类可以不指定包名;使用类类型表达式还可以进行访问类静态方法及类静态字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void test2_10() {
    //java.lang包类访问
    Class<String> result1 = parser.parseExpression("T(String)").getValue(Class.class);
    System.out.println("result1=" + result1);
    //其他包类访问
    String expression2 = "T(com.qunar.scm.spel.SpelParser)";
    Class<String> result2 = parser.parseExpression(expression2).getValue(Class.class);
    System.out.println("result2=" + result2);
    //类静态字段访问
    int result3=parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class);
    System.out.println("result3=" + result3);
    //类静态方法调用
    int result4 = parser.parseExpression("T(Integer).parseInt('1')").getValue(int.class);
    System.out.println("result4=" + result4);
}

2.11 类实例化

类实例化同样使用java关键字"new",类名必须是全限定名,但java.lang包内的类型除外,如String、Integer.

1
2
3
4
5
6
7
public static void test2_11() {
    String result1 = parser.parseExpression("new String('Spring SpEL')").getValue(String.class);
    System.out.println("result1=" + result1);

    Date result2 = parser.parseExpression("new java.util.Date()").getValue(Date.class);
    System.out.println("result2=" + result2);
}

2.12 instanceof表达式

SpEL支持instanceof运算符,跟Java内使用同义:

1
2
3
4
public static void test2_12() {
    boolean result1 = parser.parseExpression("'Spring SpEL' instanceof T(String)").getValue(Boolean.class);
    System.out.println("result1=" + result1);
}

2.13 变量定义及引用

变量定义通过EvaluationContext接口的setVariable(variableName, value)方法定义;在表达式中使用"#variableName"引用;

除了引用自定义变量,SpE还允许引用根对象及当前上下文对象,使用"#root"引用根对象,使用"#this"引用当前上下文对象;

1
2
3
4
5
6
7
8
9
10
11
12
public static void test2_13() {
    EvaluationContext context = new StandardEvaluationContext();
    context.setVariable("variable", "Spring SpEL");
    String result1 = parser.parseExpression("#variable").getValue(context, String.class);
    System.out.println("result1=" + result1);   //Spring SpEL

    context = new StandardEvaluationContext("Spring SpEL");
    String result2 = parser.parseExpression("#root").getValue(context, String.class);
    System.out.println("result2=" + result2);   //Spring SpEL
    String result3 = parser.parseExpression("#this").getValue(context, String.class);
    System.out.println("result3=" + result3);   //Spring SpEL
}

2.14 自定义函数

学习编写自定义的函数并将其注册,这样这个函数就可以在SpEL表达式中使用.

首先是两个工具类的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class CollectionUtils {
    /**
     * 集合中的最大元素
     * @param collection
     * @return
     */
    public static Integer maxElement(Collection<Integer> collection) {
        Integer maxElement = null;
        Iterator iterator = collection.iterator();
        while (iterator.hasNext()) {

            Integer integer = (Integer) iterator.next();

            if (maxElement == null) {
                maxElement = integer;
            } else {
                if (integer.intValue() > maxElement.intValue()) {
                    maxElement = integer;
                }
            }
        }
        return maxElement;
    }
}

public class MathUtils {
    /**
     * 测试一个数字是否为素数
     * @param number
     * @return
     */
    public static boolean isPrime(Integer number) {

        if (number == 0) {
            return false;
        }

        for (int index = 2; index < number; index++) {
            if (number % index == 0) {
                return false;
            } else {
                continue;
            }
        }
        return true;
    }
}

下面,我们首先初始化一个EvaluationContext,具体类是StandardEvaluationContext.

一个context evaluation object能用来存储任意多个对象,这些对象会在在随后的表达式解析中使用. 另外evaluation context也能用来注册用户自定义的方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void main(String[] args) throws Exception {
        ExpressionParser parser = new SpelExpressionParser();

        StandardEvaluationContext context = new StandardEvaluationContext();
        Method method = null;
        Expression expression = null;
        Boolean value = null;

        // 注册方法 isPrime() 为 prime
        method = MathUtils.class.getMethod("isPrime", Integer.class);
        context.registerFunction("prime", method);

        expression = parser.parseExpression("#prime(10)");
        value = expression.getValue(context, Boolean.class);
        System.out.println("Number 10 is prime: " + value);

        expression = parser.parseExpression("#prime(37)");
        value = expression.getValue(context, Boolean.class);
        System.out.println("Number 37 is prime: " + value);

        // 注册方法 maxElement() 为 max
        method = CollectionUtils.class.getMethod("maxElement", Collection.class);
        context.registerFunction("max", method);

        // Collection为参数maxElement()
        expression = parser.parseExpression("#max({10, 43, 45, 98, 32, 1})");
        Integer maxElement = expression.getValue(context, Integer.class);
        System.out.println("Max element in the list is : " + maxElement);
    }

注:EvaluationContext开销较大,所以多用于数据变化较少的情况。如果数据变化频繁,我们可以考虑直接引用对象(比如上例中的simple)以减小开销。

2.15 赋值表达式

SpEL即允许给自定义变量赋值,也允许给跟对象赋值,直接使用"#variableName=value"即可赋值:

1
2
3
4
5
6
7
8
9
10
11
public static void test2_15() {
    EvaluationContext context = new StandardEvaluationContext();
    //1.给自定义变量赋值
    context.setVariable("variable", "Spring SpEL");
    String result1 = parser.parseExpression("#variable").getValue(context, String.class);
    System.out.println("result1=" + result1);

    //修改
    String result2 = parser.parseExpression("#variable='ABC'").getValue(context, String.class);
    System.out.println("result2=" + result2);
}

2.16 对象属性存取及安全导航表达式

对象属性获取非常简单,即使用如"a.property.property"这种点缀式获取,SpEL对于属性名首字母是不区分大小写的;修改对象属性值则可以通过赋值表达式或Expression接口的setValue方法修改。

对于当前上下文对象属性及方法访问,可以直接使用属性或方法名访问,注意此处属性名首字母不区分大小写。

1
2
3
4
5
6
7
8
9
10
11
public static void test2_16() {
    ExpressionParser parser = new SpelExpressionParser();
    // 1.访问root对象属性
    Date date = new Date();
    System.out.println("date=" + date);
    StandardEvaluationContext context = new StandardEvaluationContext(date);
    int result1 = parser.parseExpression("Month").getValue(context, int.class);
    System.out.println("result1=" + result1);
    int result2 = parser.parseExpression("month").getValue(context, int.class);
    System.out.println("result2=" + result2);
}

SpEL还引入了Groovy语言中的安全导航运算符"(对象|属性)?.属性",用来避免但"?.“前边的表达式为null时抛出空指针异常,而是返回null;

1
2
3
4
5
6
7
public static void test2_16() {
    ExpressionParser parser = new SpelExpressionParser();
    // 2.安全访问
    context.setRootObject(null);
    Object result3 = parser.parseExpression("#root?.year").getValue(context, Object.class);
    System.out.println("result3=" + result3);   //null
}

2.17 对象方法调用

对象方法调用更简单,跟Java语法一样;如"‘Spring’.substring(2,4)“将返回"ri";而对于根对象可以直接调用方法;

1
2
3
4
5
6
7
8
9
10
public static void test2_17() {
    String result1 = parser.parseExpression("'Spring SpEL'.substring(2,4)").getValue(String.class);
    System.out.println("result1=" + result1);

    //root对象date方法"getYear"可以直接调用
    Date date = new Date();
    StandardEvaluationContext context = new StandardEvaluationContext(date);
    int result2 = parser.parseExpression("getMonth()").getValue(context, int.class);
    System.out.println("result2=" + result2);
}

2.18 Bean引用

SpEL支持使用"@“符号来引用Bean,在引用Bean时需要使用BeanResolver接口实现来查找Bean,Spring提供BeanFactoryResolver实现;

1
2
3
4
5
6
7
8
9
public static void test2_18() {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
    ctx.refresh();
    ExpressionParser parser = new SpelExpressionParser();
    StandardEvaluationContext context = new StandardEvaluationContext();
    context.setBeanResolver(new BeanFactoryResolver(ctx));
    Properties result1 = parser.parseExpression("@systemProperties").getValue(context, Properties.class);
    System.out.println("result1=" + result1);
}

在示例中我们首先初始化了一个IoC容器,ClassPathXmlApplicationContext实现默认会把"System.getProperties()“注册为"systemProperties"Bean,因此我们使用 ”@systemProperties"来引用该Bean。

2.19 内联List

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void test2_19() {
    // 将返回不可修改的空List
    List<Integer> result0 = parser.parseExpression("{}").getValue(List.class);
    System.out.println("result0=" + result0);   //[]

    //对于字面量列表也将返回不可修改的List
    List<Integer> result1 = parser.parseExpression("{1,2,3}").getValue(List.class);
    System.out.println("result1=" + result1);   //[1, 2, 3]
    Assert.assertEquals(new Integer(1), result1.get(0));
    try {
        result1.set(0, 2);
        //不可能执行到这,对于字面量列表不可修改
    } catch (Exception e) {
        e.printStackTrace();
    }

    //对于列表中只要有一个不是字面量表达式,将只返回原始List, 会进行不可修改处理
    String expression3 = "\{\{1+2,2+4\},\{3,4+4\}\}";
    List<List<Integer>> result2 = parser.parseExpression(expression3).getValue(List.class);
    System.out.println("result2=" + result2);   //[[3, 6], [3, 8]]
}

2.20 内联数组

和Java 数组定义类似,只是在定义时进行多维数组初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void test2_20() {
    //定义一维数组并初始化
    int[] result1 = parser.parseExpression("new int[1]{8}").getValue(int[].class);
    System.out.println("result1.length=" + result1.length + ",result1=" + result1);

    // 定义多维数组但不初始化, 多维数组不能初始化
    String expression1 = "new int[1][2]";
    //String expression2 = "new int[1][2]\{\{1}\{2\}\}"; //多维数组不能初始化
    int[][] result2 = parser.parseExpression(expression1).getValue(int[][].class);
    System.out.println("result2.length=" + result2.length + ",result2=" + result2);

    //解析到多维数据
    String expression3 = "\{\{3,4\},\{5,6\}\}";
    int[][] result3 = parser.parseExpression(expression3).getValue(int[][].class);
    System.out.println("result3=" + result3);   //[[3, 6], [3, 8]]
    for (int[] row : result3) {
        for(int data : row) {
            System.out.print(data + "   ");
        }
        System.out.println();
    }
}

2.21 集合,字典元素访问

SpEL目前支持所有集合类型和字典类型的元素访问,使用"集合[索引]“访问集合元素,使用"map[key]"访问字典元素;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void test2_21() {
    // SpEL内联List访问
    int result1 = parser.parseExpression("{1,2,3}[0]").getValue(int.class);
    System.out.println("result1=" + result1);   //1

    EvaluationContext context = new StandardEvaluationContext();

    //SpEL目前支持所有集合类型的访问
    Collection<Integer> collection = new HashSet<Integer>();
    collection.add(1);
    collection.add(2);
    context.setVariable("collection", collection);
    int result2 = parser.parseExpression("#collection[1]").getValue(context, int.class);
    System.out.println("result2=" + result2);   //2

    //SpEL对Map字典元素访问的支持
    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("A", 3);
    context.setVariable("map", map);
    int result3 = parser.parseExpression("#map['A']").getValue(context, int.class);
    System.out.println("result3=" + result3);
}

2.22 列表,字典,数组元素修改

可以使用赋值表达式或Expression接口的setValue方法修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void test2_22() {
    EvaluationContext context = new StandardEvaluationContext();

    //1.修改数组元素值
    int[] array = new int[] {1, 2};
    context.setVariable("array", array);
    int result1 = parser.parseExpression("#array[1] = 3").getValue(context, int.class);
    System.out.println("result1=" + result1);   //3

    //2.修改集合值
    Collection<Integer> collection = new ArrayList<Integer>();
    collection.add(1);
    collection.add(2);
    context.setVariable("collection", collection);
    int result2 = parser.parseExpression("#collection[1] = 4").getValue(context, int.class);
    System.out.println("result2=" + result2);   //4
    parser.parseExpression("#collection[1]").setValue(context, 5);
    int result3 = parser.parseExpression("#collection[1]").getValue(context, int.class);
    System.out.println("result3=" + result3);   //5

    //3.修改map元素值
    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("A", 1);
    context.setVariable("map", map);
    int result4 = parser.parseExpression("#map['A'] = 6").getValue(context, int.class);
    System.out.println("result4=" + result4);   //6
}

2.23 集合投影

在SQL中投影指从表中选择出列,而在SpEL指根据集合中的元素中通过选择来构造另一个集合,该集合和原集合具有相同数量的元素;SpEL使用"(list|map).![投影表达式]“来进行投影运算.

对于集合或数组使用如上表达式进行投影运算,其中投影表达式中"#this"代表每个集合或数组元素,可以使用比如"#this.property"来获取集合元素的属性,其中"#this"可以省略。

SpEL投影运算还支持Map投影,但Map投影最终只能得到List结果,如上所示,对于投影表达式中的"#this"将是Map.Entry,所以可以使用"value"来获取值,使用"key"来获取键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void test2_23() {
    EvaluationContext context = new StandardEvaluationContext();

    //1.首先准备测试数据
    Collection<Integer> collection = new ArrayList<Integer>();
    collection.add(4);
    collection.add(5);

    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("A", 1);
    map.put("B", 2);

    //2.collection中每个值加1
    context.setVariable("collection", collection);
    Collection<Integer> result1 = parser.parseExpression("#collection.![#this+1]").getValue(context,
            Collection.class);
    System.out.println("result1.size()=" + result1.size() + ", result1=" + result1);//result1.size()=2, result1=[5, 6]

    //3.测试map
    context.setVariable("map", map);
    List<Integer> result2 = parser.parseExpression("#map.![value+1]").getValue(context, List.class);
    System.out.println("result2.size()=" + result2.size() + ", result2=" + result2);    //result2.size()=2, result2=[2, 3]
}

2.24 集合选择

在SQL中指使用select进行选择行数据,而在SpEL指根据原集合通过条件表达式选择出满足条件的元素并构造为新的集合,SpEL使用"(list|map).?[选择表达式]“,其中选择表达式结果必须是boolean类型,如果true则选择的元素将添加到新集合中,false将不添加到新集合中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public static void test2_24() {
    EvaluationContext context = new StandardEvaluationContext();

    // 1.首先准备测试数据
    Collection<Integer> collection = new ArrayList<Integer>();
    collection.add(4);
    collection.add(5);

    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("A", 1);
    map.put("B", 2);

    // 2.集合或数组测试:出集合元素值大于4的所有元素
    context.setVariable("collection", collection);
    Collection<Integer> result1 = parser.parseExpression("#collection.?[#this>4]").getValue(context,
            Collection.class);
    System.out.println("result1.size()=" + result1.size() + ", result1=" + result1); // result1.size()=1, result1=[5]

    // 3.字典测试:选择键值不等于"A"的,注意map选择表达式中"#this"是Map.Entry类型
    context.setVariable("map", map);
    Map<String, Integer> result2 = parser.parseExpression("#map.?[#this.key != 'A']").getValue(context, Map.class);
    System.out.println("result2=" + result2); // result2={B=2}

    List<Integer> result3 = parser.parseExpression("#map.?[key != 'A'].![value+1]").getValue(context, List.class);
    System.out.println("result3.size()=" + result3.size() + ", result3=" + result3); // result3.size()=1,
}

2.25 表达式模板

3.Spring Cache

Spring 3.1引入了激动人心的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache等),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。

Spring Cache是使用SpEL表达式的一个地方.将会另起一篇单独介绍Sping Cache.

4.参考

http://sishuok.com/forum/blogPost/list/2463.html