SpEL表达式学习笔记

发表信息: by

SpEL表达式学习笔记

1. 介绍

Spring表达式语言(简称SpEl)是一个支持查询和操作运行时对象的功能强大的表达式语言。
Spel 创建的初衷是了给 Spring 社区提供一种简单而高效的表达式语言,一种可贯穿整个 Spring 产品组的语言。这种语言的特性应基于 Spring 产品的需求而设计。

2. 功能和案例

2.1 文字表达式

支持简单的变量。

package me.int32.test.spel;

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class Main {

    public static void main(String[] args) {
        ExpressionParser expressionParser = new SpelExpressionParser();
        Expression expression = expressionParser.parseExpression("'hello world'");

        System.out.println(expression.getValue());
    }

}

// >> hello world

SpEL支持很多功能特性,如调用方法,访问属性,调用构造函数。

package me.int32.test.spel;

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class Main {

    public static void main(String[] args) {
        ExpressionParser expressionParser = new SpelExpressionParser();
        Expression expression = expressionParser.parseExpression("'hello world'.concat('!')");

        System.out.println(expression.getValue());
    }

}

// >>> hello world!
package me.int32.test.spel;

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class Main {

    public static void main(String[] args) {
        ExpressionParser expressionParser = new SpelExpressionParser();

        Expression expression = expressionParser.parseExpression("new String('hello world').toUpperCase()");

        System.out.println(expression.getValue());
    }

}

// >>> HELLO WORLD

SpEL可以获取bean对象的属性, 注意,必须是标准bean才可以.

package me.int32.test.spel;

import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class Main {

    static class User {

        private String name;
        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }

    public static void main(String[] args) {
        User user = new User("gc", 23);
        EvaluationContext context = new StandardEvaluationContext(user);
        ExpressionParser expressionParser = new SpelExpressionParser();
        Expression expression = expressionParser.parseExpression("name");

        System.out.println(expression.getValue(context));

           user.setName("gc2");
           System.out.println(expression.getValue(context));

    }

}

// >>> gc
// >>> gc2

上面这种方式,跟下面这种方式,达到的效果是一样的。

package me.int32.test.spel;

import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class Main {

    static class User {

        private String name;
        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }

    public static void main(String[] args) {
        User user = new User("gc", 23);
        ExpressionParser expressionParser = new SpelExpressionParser();
        Expression expression = expressionParser.parseExpression("name");

        System.out.println(expression.getValue(user));

        user.setName("gc2");
        System.out.println(expression.getValue(user));
    }
}
// >>> gc
// >>> gc2

那上面两种区别在哪呢? 区别在于如果需要重复使用一个对象,可以构造一个StandardEvaluationContext , 它本身会有一些优化,虽然构造它比较昂贵,但使用起来会更快,如果只使用一次那可以不用它,如果会重复使用,建议使用它。

当然,SpEL表达时可以修改对象内的值。

package me.int32.test.spel;

import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class Main {

    static class User {

        private String name;
        private int age;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }

    public static void main(String[] args) {
        User user = new User("gc", 23);
        ExpressionParser expressionParser = new SpelExpressionParser();
        Expression expression = expressionParser.parseExpression("name");

        System.out.println(expression.getValue(user));
        expression.setValue(user, "gc2");
        System.out.println(expression.getValue(user));
    }

}

// >>> gc
// >>> gc2

2.2 表达时支持定义bean

public static class FieldValueTestBean{

  @Value("#{ systemProperties['user.region'] }")
  private String defaultLocale;

  public void setDefaultLocale(String defaultLocale){
    this.defaultLocale = defaultLocale;
  }
  public String getDefaultLocale(){
    return this.defaultLocale;
  }
}
public static class PropertyValueTestBean{

  private String defaultLocale;

  @Value("#{ systemProperties['user.region'] }")
  public void setDefaultLocale(String defaultLocale){
    this.defaultLocale = defaultLocale;
  }

  public String getDefaultLocale() {
    return this.defaultLocale;
  }
}
public class SimpleMovieLister{

  private MovieFinder movieFinder;
  private String defaultLocale;

  @Autowired
  public void configure(MovieFinder movieFinder, 
                        @Value("#{ systemProperties['user.region'] }") String defaultLocale) {
      this.movieFinder = movieFinder;
      this.defaultLocale = defaultLocale;
  }
}

2.3 文字表达式

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

Numbers 支持 负号、科学技数法、小数点等,默认使用 Double.parseDouble() 解析

2.4 属性,数组,List,Map,索引

package me.int32.test.spel;

import com.google.common.collect.Maps;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class Main {

    @Data
    @AllArgsConstructor
    static class User {

        private String name;
        private int age;
        private String[] phone;
        private List<String> address;
        private Map<String, Integer> has;
    }

    public static void main(String[] args) {
        User user = new User("gc", 23, new String[]{"123456"}, Arrays.asList("中国 上海", "中国 北京"), new HashMap<>());

        System.out.println(new SpelExpressionParser().parseExpression("age + 10")
                .getValue(user, Integer.class)); // 属性
        System.out.println(new SpelExpressionParser().parseExpression("phone[0]")
                .getValue(user, String.class)); // 数组
        System.out.println(new SpelExpressionParser().parseExpression("address[0]")
                .getValue(user, String.class)); // 列表
        System.out.println(new SpelExpressionParser().parseExpression("[0].phone[0]")
                .getValue(new User[]{user}, String.class)); // 数组
        System.out.println(new SpelExpressionParser().parseExpression("has['书包'] == null")
                .getValue(user, Boolean.class)); // map
    }
}

// >>> 33
// >>> 123456
// >>> 中国 上海
// >>> 123456
// >>> true

2.5 内联

public static void main(String[] args) {
    System.out.println(new SpelExpressionParser().parseExpression("{1,2,3,4,5}").getValue());
    System.out.println(new SpelExpressionParser().parseExpression("new int[]{1,2,3,4,5}").getValue());
}

// >>> [1, 2, 3, 4, 5]
// >>> [I@5f5a92bb

2.6 方法调用

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

2.7 操作符

package me.int32.test.spel;

import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class Main {

    @Data
    @AllArgsConstructor
    static class User {

        private String name;
        private int age;
        private String[] phone;
        private List<String> address;
        private Map<String, Integer> has;
    }

    public static void main(String[] args) {

        SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
        System.out.println(spelExpressionParser.parseExpression("1 < 3").getValue());
        System.out.println(
                spelExpressionParser.parseExpression("T(java.lang.Math).random() < T(java.lang.Math).random()")
                        .getValue());
        System.out.println(spelExpressionParser.parseExpression("'black' < 'apple'").getValue());
        System.out.println(spelExpressionParser.parseExpression("'xyz' instanceof T(String)").getValue());
        System.out.println(spelExpressionParser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue());
        System.out.println(spelExpressionParser.parseExpression("true and false").getValue());
        System.out.println(spelExpressionParser.parseExpression("true and !false").getValue());
        System.out.println(spelExpressionParser.parseExpression("1 + 2 - 3 + 4 * 5 * (6 + 7)").getValue());

    }
}

// >>> true
// >>> true
// >>> false
// >>> true
// >>> false

有时候为了避免产生歧义,可以使用嵌入式操作符: ` lt ('<'), gt ('>'), le ('<='), ge ('>='), eq ('=='), ne ('!='), div ('/'), mod ('%'), not ('!')`.

2.8 类型

T 操作能够获取到 java.lang.Class ,可以直接调用静态方法,StandardEvaluationContext 已经内置了java.lang 包,也就是说T()在表示java.lang 的时候,不需要指定包路径,其他的路径,必须手动指定全。

package me.int32.test.spel;

import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class Main {

    @Data
    @AllArgsConstructor
    static class User {

        private String name;
        private int age;
        private String[] phone;
        private List<String> address;
        private Map<String, Integer> has;
    }

    public static void main(String[] args) {
        SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
        System.out.println(spelExpressionParser.parseExpression("T(String)").getValue(Class.class));
        System.out.println(spelExpressionParser.parseExpression("T(java.util.Date)").getValue(Class.class));
        System.out.println(spelExpressionParser.parseExpression("T(Math).random()").getValue());
        System.out.println(spelExpressionParser.parseExpression(
                        "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
                .getValue(Boolean.class));
    }
}

// >>> class java.lang.String
// >>> class java.util.Date
// >>> 0.39669349863302106
// >>> true

2.9 构造器

能够调用构造器,除了原生类型喝String之外,必须写明类的全限定名。

package me.int32.test.spel;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Random;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class Main {

    @Data
    @AllArgsConstructor
    static class User {

        private String name;
        private int age;
        private String[] phone;
        private List<String> address;
        private Map<String, Integer> has;

    }

    public static void main(String[] args) {
        SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
        System.out.println(spelExpressionParser.parseExpression(
                "new java.lang.StringBuffer().append('hello,').append('world!').toString()").getValue(
                java.lang.String.class));
        new Random().nextInt(10);
        System.out.println(spelExpressionParser.parseExpression(
                        "(new java.util.Random().nextInt(10)) < (new java.util.Random().nextInt(10))")
                .getValue(Boolean.class));
    }
}

// >>> hello,world!
// >>> true

2.10 变量

  • 支持设置变量到上下文里面
  • #this 总是指定当前对象
  • #root 总是指定到对象
package me.int32.test.spel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class Main {

    @Data
    @AllArgsConstructor
    static class User {

        private String name;
        private int age;
        private String[] phone;
        private List<String> address;
        private Map<String, Integer> has;

    }

    public static void main(String[] args) {

        StandardEvaluationContext context = new StandardEvaluationContext();
        context.setVariable("name", "gc");
        User user = new User("gc2", 10, new String[]{"123"}, new ArrayList<String>() , new HashMap<>());
        context.setRootObject(new ArrayList<User>() );

        SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

        System.out.println(spelExpressionParser.parseExpression(
                "#root[0].name = #name").getValue(context));

        System.out.println(user);
    }
}

// >>> gc
// >>> Main.User(name=gc, age=10, phone=[123], address=[hello world!], has={})

2.11 函数调用

StandardEvaluationContext 支持注册函数,执行函数调用

package me.int32.test.spel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class Main {

    @Data
    @AllArgsConstructor
    static class User {

        private String name;
        private int age;
        private String[] phone;
        private List<String> address;
        private Map<String, Integer> has;

    }

    public static void main(String[] args) throws NoSuchMethodException {

        StandardEvaluationContext context = new StandardEvaluationContext();
        context.registerFunction("reverseString", Main.class.getDeclaredMethod(
                "reverseString", String.class
        ));

        SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
        System.out.println(spelExpressionParser.parseExpression(
                "#reverseString('hello world')").getValue(context));
    }

    public static String reverseString(String input) {
        StringBuilder backwards = new StringBuilder();
        for (int i = 0; i < input.length(); i++) {
            backwards.append(input.charAt(input.length() - 1 - i));
        }
        return backwards.toString();
    }
}

// >>> dlrow olleh

2.12 Bean 引用

如果在执行上下文中已经配置好了bean解析器,可以使用@来使用查找和使用bean

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

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

2.13 If-Then-Else

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

expression = "isMember(#queryName)? #queryName + ' is a member of the ' " + 
             "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";

String queryResultString = 
                    parser.parseExpression(expression).getValue(societyContext, String.class);

// >>> queryResultString = "Nikola Tesla is a member of the IEEE Society"

2.14 三元操作符优化

ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("#name?:'Unknown'").getValue(String.class);
// 等价:String displayName = name != null ? name : "Unknown";

System.out.println(name);  

// >>> 'Unknown'

下面是一些更复杂的例子

ExpressionParser parser = new SpelExpressionParser();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);

String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);

System.out.println(name); 

tesla.setName(null);

name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);

System.out.println(name); 

// >>> Nikola Tesla
// >>> Elvis Presley

2.15 针对NPE优化的操作符

ExpressionParser parser = new SpelExpressionParser();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));

StandardEvaluationContext context = new StandardEvaluationContext(tesla);

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

tesla.setPlaceOfBirth(null);

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

System.out.println(city); 

// >>> Smiljan
// >> null 

2.16 容器选择

package me.int32.test.spel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class Main {

    @Data
    @AllArgsConstructor
    static class User {

        private String name;
        private int age;
        private String[] phone;
        private List<String> address;
        private Map<String, Integer> has;

    }

    public static void main(String[] args) throws NoSuchMethodException {
        SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
        System.out.println(
                spelExpressionParser.parseExpression("#root.?[#this > 10]")
                        .getValue(new ArrayList<Integer>() ));
        System.out.println(
                spelExpressionParser.parseExpression("#root.?[#this.value > 10]")
                        .getValue(new HashMap<String, Integer>() ));
    }
}


// >>> [11, 12]
// >>> {gc=11}

map 每一个元素是 Entry

2.17 容器映射

package me.int32.test.spel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class Main {

    @Data
    @AllArgsConstructor
    static class User {

        private String name;
        private int age;
        private String[] phone;
        private List<String> address;
        private Map<String, Integer> has;

    }

    public static void main(String[] args) throws NoSuchMethodException {
        SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
        User user1 = new User("gc1", 3, null, null, null);
        User user2 = new User("gc2", 11, null, null, null);
        User user3 = new User("gc3", 13, null, null, null);
        List<User> userList = new ArrayList<>();
        userList.add(user1);
        userList.add(user2);
        userList.add(user3);

        System.out.println(spelExpressionParser.parseExpression(
                "#root.?[#this.age > 10].![new java.lang.StringBuffer().append(#this.name + '投影').toString()]").getValue(userList));
    }
}

// >>> [gc2投影, gc3投影]

2.18 表达式模版

package me.int32.test.spel;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class Main {

    @Data
    @AllArgsConstructor
    static class User {

        private String name;
        private int age;
        private String[] phone;
        private List<String> address;
        private Map<String, Integer> has;

    }

    public static void main(String[] args) throws NoSuchMethodException {
        SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
        User user = new User("gc", 3, null, null, null);

        System.out.println(spelExpressionParser.parseExpression(
                        "'hello ' + #root.name")
                .getValue(user));
        System.out.println(spelExpressionParser.parseExpression(
                        "hello #{#root.name}", new TemplateParserContext())
                .getValue(user));
        System.out.println(spelExpressionParser.parseExpression(
                        "hello !{#root.name}!", new TemplateParserContext("!{", "}!"))
                .getValue(user));
    }
}

// >>> hello gc
// >>> hello gc
// >>> hello gc

X. 参考文档