SpEL表达式使用

young 964 2021-11-21

Spring Expression Language

SpEL是Spring提供的一种的表达式语言,支持在运行时查询和操作对象。

SpEL并不直接与Spring相关联,可以独立使用。

SpEL解析器

SpEL提供了对应的解析器SpelExpressionParser用于解析SpEL表达式

EvaluationContext

EvaluationContext用于计算表达式以解析属性、方法、字段,并帮助执行类型转换。

Spring提供了两个实现SimpleEvaluationContextStandardEvaluationContext

表达式类型

Literal Expressions 文字表达式

文字表达式支持字符串、数值、布尔值、空值。字符串由单引号进行引用,要将单引号本身放在字符串中,需使用两个单引号。

数字类型支持使用负号、指数表示法、小数点,默认使用Double.parseDouble()解析数值

ExpressionParser parser = new SpelExpressionParser();

String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
System.out.println(helloWorld); // Hello World

String word = (String) parser.parseExpression("'''word'''").getValue();
System.out.println(word); // 'word'

double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
System.out.println(avogadrosNumber); // 6.0221415E23

int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
System.out.println(maxValue); // 2147483647

boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
System.out.println(trueValue); // true

Object nullValue = parser.parseExpression("null").getValue();
System.out.println(nullValue==null); // true

Properties, Arrays, Lists, Maps, and Indexers 属性,数组,列表,Map,索引

通过点进行属性获取,嵌套属性使用点对属性进行分隔

properties 属性

@AllArgsConstructor
@NoArgsConstructor
@Data
public static class TestVo{
    private String name;
    private int age;
    private Date birth;
    private Addr addr;
}

@AllArgsConstructor
@NoArgsConstructor
@Data
public static class Addr{
    private String province;
    private String city;
}
TestVo testVo = new TestVo("young", 20, new Date(), new Addr("beijing", "beijing"));
ExpressionParser parser = new SpelExpressionParser();
int year = (int) parser.parseExpression("birth.year+1900").getValue(testVo);
System.out.println(year); // 2021
String city = (String) parser.parseExpression("addr.city").getValue(testVo);
System.out.println(city); //北京

数组、列表

数组,列表,可以通过[]指定下标来获取对应的值,其余与properties一样

@AllArgsConstructor
@Data
public static class TestList{
    private List<Integer> listField;
}
List<Integer> list = IntStream.range(0, 10).boxed().collect(Collectors.toList()); // 0-9
int[] arr = IntStream.range(0, 10).toArray(); // 0-9

ExpressionParser parser = new SpelExpressionParser();

System.out.println(parser.parseExpression("[5]").getValue(list));// 5
System.out.println(parser.parseExpression("[9]").getValue(arr)); // 9

TestList testList = new TestList(list);
System.out.println(parser.parseExpression("listField[4]").getValue(testList)); // 4

Map

map的内容是通过[]中指定key来获取值的,对象类型也可以用同样的方式获取,字符串类型的key要用单引号引起来

TestVo testVo = new TestVo("young", 20, new Date(), new Addr("shanxi", "xi'an"));

ExpressionParser parser = new SpelExpressionParser();
System.out.println(parser.parseExpression("['name']").getValue(testVo)); // young 
System.out.println(parser.parseExpression("addr['city']").getValue(testVo)); // xi'an


Map<String,String> map = new HashMap<>();
map.put("beijing","beijing");
map.put("shanxi","xi'an");
map.put("jiangsu","南京");

System.out.println(parser.parseExpression("['jiangsu']").getValue(map)); // 南京

Inline Lists 内联列表

可以使用{}直接在表达式中表示列表,{}表示一个空列表,如果完全由固定文字组成,则SpEL会创建一个列表常量

ExpressionParser parser = new SpelExpressionParser();

List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue();
System.out.println(numbers); // [1, 2, 3, 4]

List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue();
System.out.println(listOfLists); // [[a, b], [x, y]]

Inline Maps 内联Map

可以使用{key:value}直接在表达式中表示Map,{:}表示一个空Map

ExpressionParser parser = new SpelExpressionParser();

Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue();
System.out.println(inventorInfo); // {name=Nikola, dob=10-July-1856}

Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue();
System.out.println(mapOfMaps); // {name={first=Nikola, last=Tesla}, dob={day=10, month=July, year=1856}}

Array Construction 构建数组

可以使用Java的数组构建语法构建表示一个数组,构建多维数组时,不能进行初始化操作

ExpressionParser parser = new SpelExpressionParser();

int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue();

int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue();

int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue();

Methods 方法

可以调用java的方法,支持可变参数

public static class TestMethod{
    public boolean isString(Object obj){
        return obj!=null && obj.getClass().equals(String.class);
    }
}
ExpressionParser parser = new SpelExpressionParser();
System.out.println(parser.parseExpression("'Hello World'.concat('!')").getValue()); // Hello World!
System.out.println(parser.parseExpression("'Hello World'.bytes.length").getValue()); // 11
System.out.println(parser.parseExpression("new String('hello world').toUpperCase()").getValue()); // HELLO WORLD
System.out.println(parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class)); // bc

TestMethod testMethod = new TestMethod();
System.out.println(parser.parseExpression("isString(123)").getValue(testMethod)); // false
System.out.println(parser.parseExpression("isString('123')").getValue(testMethod));// true

Operators 运算符

关系运算符

支持等于,不等于,大于,大于等于,小于,小于等于这类标准关系运算符,还支持instanceof和基于正则表达式的matches运算

对于null值,任何非null的值都大于null,及x>null恒等于true,x<null恒等于false

ExpressionParser parser = new SpelExpressionParser();

System.out.println(parser.parseExpression("2 == 2").getValue(Boolean.class)); // true

System.out.println(parser.parseExpression("2 < -5.0").getValue(Boolean.class)); // false

System.out.println(parser.parseExpression("'black' < 'block'").getValue(Boolean.class)); // true

System.out.println(parser.parseExpression("'black' == 'black'").getValue(Boolean.class)); // true

System.out.println(parser.parseExpression("'xyz' instanceof T(Integer)").getValue(Boolean.class)); // false

System.out.println(parser.parseExpression("'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class)); // true

System.out.println(parser.parseExpression("'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class)); // false

System.out.println(parser.parseExpression("1 instanceof T(int)").getValue(Boolean.class)); // false

System.out.println(parser.parseExpression("1 instanceof T(Integer)").getValue(Boolean.class));// true

SpEL中 T表示类型

基本类型会被处理成包装类,所以instanceof int为false

为了避免符号对嵌入表达式的文档类型(如xml)有特殊含义是,表达式可以使用转义符,且不区分大小写

  • lt (<)
  • ge (>)
  • le (<=)
  • ge (>=)
  • eq (==)
  • ne (!=)
  • div (/)
  • mod (%)
  • not (!)

逻辑运算符

  • and (&&)
  • or (||)
  • not (!)
ExpressionParser parser = new SpelExpressionParser();
TestMethod testMethod = new TestMethod();

System.out.println(parser.parseExpression("true and false").getValue(Boolean.class)); // false

String expression = "isString('abc') and isString(123)";
System.out.println(parser.parseExpression(expression).getValue(testMethod, Boolean.class)); // false

System.out.println(parser.parseExpression("true or false").getValue(Boolean.class)); // true

expression = "isString('abc') or isString(123)";
System.out.println(parser.parseExpression(expression).getValue(testMethod, Boolean.class)); // true

System.out.println(parser.parseExpression("!true").getValue(Boolean.class)); // false

expression = "isString('abc') and !isString(123)";
System.out.println(parser.parseExpression(expression).getValue(testMethod, Boolean.class)); // true

数学运算符

可以对数字和字符串使用加法运算符(+),对数字可以使用减法(-),乘法(*)、除法(/)、取模(%),指数幂(^)运算,按标准运算符优先级进行执行

ExpressionParser parser = new SpelExpressionParser();

System.out.println(parser.parseExpression("1 + 1").getValue(Integer.class)); // 2

System.out.println(parser.parseExpression("'test' + ' ' + 'string'").getValue(String.class)); // test string

System.out.println(parser.parseExpression("1 - -3").getValue(Integer.class)); // 4 

System.out.println(parser.parseExpression("1000.00 - 1e4").getValue(Double.class)); // -9000.0

System.out.println(parser.parseExpression("-2 * -3").getValue(Integer.class)); // 6

System.out.println(parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class)); // 24.0

System.out.println(parser.parseExpression("6 / -3").getValue(Integer.class)); // -2

System.out.println(parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class)); // 1.0

System.out.println(parser.parseExpression("7 % 4").getValue(Integer.class)); // 3

System.out.println(parser.parseExpression("8 / 5 % 2").getValue(Integer.class)); // 1

System.out.println(parser.parseExpression("1+2-3*8").getValue(Integer.class)); // -21

赋值运算符

设置属性,可以使用赋值运算符=,可以使用getValue和setValue两种方法

ExpressionParser parser = new SpelExpressionParser();
TestVo testVo = new TestVo();
parser.parseExpression("name").setValue(testVo, "young"); 
System.out.println(testVo); // TestMain.TestVo(name=young, age=0, birth=null, addr=null)

String name = parser.parseExpression(
        "name = 'jordan'").getValue(testVo, String.class);
System.out.println(name); // jordan
System.out.println(testVo); //TestMain.TestVo(name=jordan, age=0, birth=null, addr=null)

Types 类

可以使用T运算符来指定java.lang.Class的实例,静态方法也通过此运算符调用。除了java.lang包下的类,其余的类均需使用全限定类名

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

String value = parser.parseExpression("T(String).valueOf(100)").getValue(String.class); // 100

Constructors 构造函数

可使用new调用构造函数,除了java.lang包中的类型,其他类型都应该写全限定类名

Variables 变量

可以使用#variableName引用变量,变量是使用实现setVariable上的方法设置的EvaluationContext

变量名规则由以下描述的一个或多个字符组成

  • 字母A到Z或a到z
  • 数字0到9
  • 下划线 _
  • 美元符号 $
ExpressionParser parser = new SpelExpressionParser();
SimpleEvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("testName","young");
TestVo testVo = new TestVo();
parser.parseExpression("name = #testName").getValue(context,testVo);
System.out.println(testVo); // TestMain.TestVo(name=young, age=0, birth=null, addr=null)

#this和#root

#root 表示跟对象,可以通过EvaluationContext设置或在调用方法时指定

#this 表示当前变量

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
List<Integer> primes = new ArrayList<>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
context.setVariable("primes", primes);
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context); // [11, 13, 17]
System.out.println(primesGreaterThanTen);
Map<String,Object> map = new HashMap<>();
map.put("code",5);
context.setVariable("primesMap", map);
// context.setRootObject(map);
System.out.println(parser.parseExpression("#primes.?[#this>#root['code']]").getValue(context,map)); // [7, 11, 13, 17]

Functions 方法

可以自定义方法来扩展SpEL,通过EvaluationContext进行注册,被注册的方法必须是被static修饰

public static class TestMethod{
    public static boolean isString(Object obj){
        return obj!=null && obj.getClass().equals(String.class);
    }
}
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", TestMethod.class.getDeclaredMethod("isString", Object.class));
ExpressionParser parser = new SpelExpressionParser();
System.out.println(parser.parseExpression("#myFunction(123)").getValue(context)); // false

Bean References Bean引用

如果EvaluationContext配置了BeanResolver,则可以通过@符号加bean名称获取bean,如果要访问工厂Bean(FactoryBean),需在bean名称前加一个&符号

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

// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").getValue(context);
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);

Ternary Operator (If-Then-Else) 三目运算符

String falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String.class); // falseExp
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"

Elvis Operator Elvis运算符

Elvis 运算符是三元运算符语法的缩写,来源于Groovy语言

语法:变量?:返回值

如果变量是null,则返回返回值,否则返回变量本身

ExpressionParser parser = new SpelExpressionParser();
String name = parser.parseExpression("name?:'Unknown'").getValue(new TestVo(), String.class);
System.out.println(name);  // 'Unknown'
TestVo testVo = new TestVo("young",0,null,null);
System.out.println(parser.parseExpression("name?:'jordan'").getValue(testVo, String.class)); // young
testVo.setName(null);
System.out.println(parser.parseExpression("name?:'jordan'").getValue(testVo, String.class)); // jordan

可以使用这种方法设置默认值

Safe Navigation Operator 安全导航运算符

用于避免空指针操作,来源于Groovy语言

语法:对象?.属性

当对象为null时,返回null,否则取属性中的值,有点类似于Optional.ofNullable(对象).map(对象.属性).orElse(null)的操作

ExpressionParser parser = new SpelExpressionParser();
TestVo testVo = new TestVo("young", 0, new Date(), new Addr("shanxi", "xian"));
System.out.println(parser.parseExpression("addr?.city").getValue(testVo, String.class)); // xian
testVo.setAddr(null);
System.out.println(parser.parseExpression("addr?.city").getValue(testVo, String.class)); // null

Collection Selection 集合查找

使用.?[selectionExpression] 进行集合过滤,返回一个满足selectionExpression的新集合

数组,map或者实现了java.lang.Iterable的类型都支持

对于数组或列表,将针对每个元素进行过滤

对于map,将基于Map.Entry进行过滤,可以通过Entry的key或者value进行查找

可以通过.^[selectionExpression]获取第一个查找到的第一个元素,.$[selectionExpression]获取最后一个元素

ExpressionParser parser = new SpelExpressionParser();
SimpleEvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

List<Integer> list = Arrays.asList(1, 3, 5, 7, 9, 11, 13, 15, 17, 19);
context.setVariable("list",list);
System.out.println(parser.parseExpression("#list.?[#this<10]").getValue(context)); // [1, 3, 5, 7, 9]

TestSelector testSelector = new TestSelector(list);
System.out.println(parser.parseExpression("code.?[#this<7]").getValue(testSelector)); // [1, 3, 5]

Map<Integer, Integer> map = list.stream().collect(Collectors.toMap(e -> e, e -> e));
context.setVariable("map",map);
System.out.println(parser.parseExpression("#map.?[value>11]").getValue(context)); // {17=17, 19=19, 13=13, 15=15}

System.out.println(parser.parseExpression("#map.^[value>15]").getValue(context));// {17=17}
System.out.println(parser.parseExpression("#map.$[value>15]").getValue(context));// {15=15}

Collection Projection

使用.![projectionExpression],将结果表达式里的数据生成一个新的集合

ExpressionParser parser = new SpelExpressionParser();
List<TestVo> list = new ArrayList<>();
list.add(new TestVo("1",1,new Date(),new Addr("shanxi","xian")));
list.add(new TestVo("2",2,new Date(),new Addr("jingsu","nanjing")));
list.add(new TestVo("3",3,new Date(),new Addr("beijing","beijing")));
SimpleEvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("list",list);
System.out.println(parser.parseExpression("#list.![addr.city]").getValue(context));

有点类似于java的Stream操作

list.stream().map(e -> e.getAddr().getCity()).collect(Collectors.toList())