参考文献

Spring Expression Language (SpEL)

支持的功能

功能 说明
Literal expressions 字面量表达式
Boolean and relational operators 布尔和关系运算符
Regular expressions 正则表达式
Class expressions 类表达式
Accessing properties, arrays, lists, and maps 访问属性、数组、列表和映射
Method invocation 方法调用
Assignment 赋值
Calling constructors 调用构造函数
Bean references Bean引用
Array construction 数组构建
Inline lists 内联列表
Inline maps 内嵌Map
Ternary operator 三元运算符
Variables 变量
User-defined functions added to the context 添加到上下文的用户定义函数
reflective invocation of Method 方法的反射调用
various cases of MethodHandle MethodHandle的各种情况
Collection projection 集合投影
Collection selection 集合选择
Templated expressions 模板化表达式

Literal expressions

  • SpEL 支持以下类型的字面量表达式.
    • strings
    • numeric values: integer (int or long), hexadecimal (int or long), real (float or double)
    • boolean values: true or false
    • null
  • 字符串可以用单引号 ( ' ) 或双引号 ( " ) 分隔.要在用单引号括起来的字符串文字中包含单引号,请使用两个相邻的单引号字符.同样,要在用双引号括起来的字符串文字中包含双引号,请使用两个相邻的双引号字符.
  • 数字支持使用负号、指数表示法和小数点.默认情况下,使用 Double.parseDouble() 解析实数.

Accessing properties, arrays, lists, and maps

  • 数组和列表的内容是通过使用方括号表示法获得的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    ExpressionParser parser = new SpelExpressionParser();
    EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

    // Inventions Array

    // evaluates to "Induction motor"
    String invention = parser.parseExpression("inventions[3]").getValue(
    context, tesla, String.class);

    // Members List

    // evaluates to "Nikola Tesla"
    String name = parser.parseExpression("members[0].name").getValue(
    context, ieee, String.class);

    // List and Array navigation
    // evaluates to "Wireless communication"
    String invention = parser.parseExpression("members[0].inventions[6]").getValue(
    context, ieee, String.class);
  • 映射的内容是通过指定括号内的文字键值来获取的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // Officer's Dictionary

    Inventor pupin = parser.parseExpression("officers['president']").getValue(
    societyContext, Inventor.class);

    // evaluates to "Idvor"
    String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
    societyContext, String.class);

    // setting values
    parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
    societyContext, "Croatia");

Inline Lists

  • 可以使用 {} 表示法直接在表达式中表达列表
1
2
3
4
// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);

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

Inline Maps

  • 可以使用 {key:value} 表示法直接在表达式中表达映射

Operators

  • Relational Operators 关系运算符
  • Logical Operators 逻辑运算符
  • Mathematical Operators 数学运算符
  • The Assignment Operator 赋值运算符
Relational Operators
  • 使用标准运算符表示法支持关系运算符(等于、不等于、小于、小于或等于、大于和大于或等于).这些运算符适用于 Number 类型以及实现 Comparable 的类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 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);

    // uses CustomValue:::compareTo
    boolean trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean.class);
    • null 的大于和小于比较遵循一个简单的规则: null 被视为无(即不为零).因此,任何其他值始终大于 null ( X > null 始终 true ),并且任何其他值都不会小于任何值( X < null ).
  • 除了标准关系运算符之外,SpEL 还支持 instanceof 和基于正则表达式的 matches 运算符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 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);
    • 请小心原始类型,因为它们会立即装箱为其包装类型.例如,如预期, 1 instanceof T(int) 计算结果为 false ,而 1 instanceof T(Integer) 计算结果为 true .
  • 每个符号运算符也可以指定为纯字母等效项.这可以避免所使用的符号对于嵌入表达式的文档类型(例如在 XML 文档中)具有特殊含义的问题.等效的文本是:

    • lt (<) lt ( < )
    • gt (>) gt ( > )
    • le (<=) le ( <= )
    • ge (>=) ge ( >= )
    • eq (==) eq ( == )
    • ne (!=) ne ( != )
    • div (/) div ( / )
    • mod (%) mod ( % )
    • not (!). not ( ! )
Logical Operators
  • SpEL 支持以下逻辑运算符:
    • and (&&) and ( && )
    • or (||) or ( || )
    • not (!) not ( ! )
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
// -- 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
  • 可以对数字和字符串使用加法运算符 ( + ).只能对数字使用减法 ( - )、乘法 ( * ) 和除法 ( / ) 运算符.还可以对数字使用模数 ( % ) 和指数幂 ( ^ ) 运算符

  • 强制执行标准运算符优先级

    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
    // 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
The Assignment Operator
  • 要设置属性,请使用赋值运算符 ( = ).这通常在对 setValue 的调用中完成,但也可以在对 getValue 的调用中完成.

    1
    2
    3
    4
    5
    6
    7
    8
    Inventor inventor = new Inventor();
    EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

    parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic");

    // alternatively
    String aleks = parser.parseExpression(
    "name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);

Types

  • 可以使用特殊的 T 运算符来指定 java.lang.Class (类型)的实例.静态方法也可以使用此运算符来调用. StandardEvaluationContext 使用 TypeLocator 查找类型, StandardTypeLocator (可以替换)是在理解 java.lang 的情况下构建的包裹.这意味着 T()java.lang 包中类型的引用不需要完全限定,但所有其他类型引用必须完全限定.
1
2
3
4
5
6
7
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);

Constructors

  • 可以使用 new 运算符调用构造函数.应该对除 java.lang 包中的类型( IntegerFloatString 等等).

    1
    2
    3
    4
    5
    6
    7
    8
    Inventor einstein = p.parseExpression(
    "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
    .getValue(Inventor.class);

    // create new Inventor instance within the add() method of List
    p.parseExpression(
    "Members.add(new org.spring.samples.spel.inventor.Inventor(
    'Albert Einstein', 'German'))").getValue(societyContext);

Variables

  • 可以使用 #variableName 语法引用表达式中的变量.变量是通过在 EvaluationContext 实现上使用 setVariable 方法来设置的
  • 有效的变量名称必须由以下一个或多个受支持的字符组成.
    • letters: A to Z and a to z
    • digits: 0 to 9
    • underscore: _
    • dollar sign: $

Bean References

  • 如果评估上下文已配置了 Bean 解析器,则可以使用 @ 符号从表达式中查找 Bean.

    1
    2
    3
    4
    5
    6
    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);
  • 要访问工厂 bean 本身,应该在 bean 名称前加上 & 符号作为前缀.

    1
    2
    3
    4
    5
    6
    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);

Safe Navigation Operator

  • 安全导航运算符用于避免 NullPointerException ,来自 Groovy 语言.通常,当拥有对对象的引用时,可能需要在访问该对象的方法或属性之前验证它是否不为null.为了避免这种情况,安全导航运算符返回null而不是抛出异常.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ExpressionParser parser = new SpelExpressionParser();
    EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

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

    String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
    System.out.println(city); // Smiljan

    tesla.setPlaceOfBirth(null);
    city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
    System.out.println(city); // null - does not throw NullPointerException!!!

Collection Selection

  • 选择使用 .?[selectionExpression] 语法.它过滤集合并返回包含原始元素子集的新集合

    • 相当于Java 8 Stream#filter操作
    1
    2
    List<Inventor> list = (List<Inventor>) parser.parseExpression(
    "members.?[nationality == 'Serbian']").getValue(societyContext);
  • 数组和任何实现 java.lang.Iterablejava.util.Map 的对象都支持选择.对于列表或数组,选择标准是针对每个单独的元素进行评估的.针对映射,根据每个映射条目(Java类型 Map.Entry 的对象)评估选择标准.每个地图条目都有其 keyvalue 可作为属性访问以在选择中使用.

  • 除了返回所有选定的元素之外,还可以仅检索第一个或最后一个元素.要获取与选择匹配的第一个元素,语法为 .^[selectionExpression] .要获取最后的匹配选择,语法为 .$[selectionExpression] .

Collection Projection

  • 投影让集合驱动子表达式的计算,结果是一个新集合.投影的语法是 .![projectionExpression] .

    • 相当于Java 8 Stream#map操作
    1
    2
    // returns ['Smiljan', 'Idvor' ]
    List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");
  • 数组和任何实现 java.lang.Iterablejava.util.Map 的对象都支持投影.当使用映射来驱动投影时,将针对映射中的每个条目(表示为 Java Map.Entry )计算投影表达式.跨地图投影的结果是一个列表,其中包含针对每个地图条目的投影表达式的评估.

使用方式

注解@Value

1
2
3
4
//@Value能修饰成员变量和方法形参
//#{}内就是表达式的内容
@Value("#{表达式}")
public String arg;

XML配置

  • 字面量赋值必须要和对应的属性类型兼容,否则会报异常。

    1
    2
    3
    4
    <bean id="xxx" class="cn.holelin.test.bean">
    <!-- 同@Value,#{}内是表达式的值,可放在property或constructor-arg内 -->
    <property name="name" value="#{表达式}">
    </bean>
  • 引用Bean、属性和方法(必须是public修饰的)

    1
    2
    3
    4
    5
    <property name="car" value="#{car}" />
    <!-- 引用其他对象的属性 -->
    <property name="carName" value="#{car.name}" />
    <!-- 引用其他对象的方法 -->
    <property name="carPrint" value="#{car.print()}" />

代码块中使用Expression

1
2
3
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();
方法调用
1
2
3
4
ExpressionParser parser = new SpelExpressionParser();
# 调用java.lang.String#concat方法
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();
属性调用
1
2
3
4
ExpressionParser parser = new SpelExpressionParser();
// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes");
byte[] bytes = (byte[]) exp.getValue();
  • SpEL 还通过使用标准点表示法(例如 prop1.prop2.prop3 )以及相应的属性值设置来支持嵌套属性.也可以访问公共字段.

组件

EvaluationContext

  • EvaluationContext 接口用于计算表达式以解析属性、方法或字段并帮助执行类型转换. Spring提供了两种实现方式.
    • SimpleEvaluationContext :公开 SpEL 语言基本功能和配置选项的子集,适用于不需要 SpEL 语言语法完整范围且应受到有意义限制的表达式类别.示例包括但不限于数据绑定表达式和基于属性的过滤器.
    • StandardEvaluationContext :公开全套 SpEL 语言功能和配置选项.可以使用它来指定默认根对象并配置每个可用的评估相关策略.
  • SimpleEvaluationContext 旨在仅支持 SpEL 语言语法的子集.它不包括 Java 类型引用、构造函数和 bean 引用.它还要求显式选择表达式中属性和方法的支持级别.默认情况下, create() 静态工厂方法仅启用对属性的读取访问.还可以获取构建器来配置所需的确切支持级别,针对以下一项或某项组合:
    • Custom PropertyAccessor only (no reflection)
    • Data binding properties for read-only access
    • Data binding properties for read and write

Type Conversion

  • 默认情况下,SpEL 使用 Spring 核心中提供的转换服务 ( org.springframework.core.convert.ConversionService ).此转换服务附带许多用于常见转换的内置转换器,但也完全可扩展,以便可以在类型之间添加自定义转换.此外,它还具有泛型意识.这意味着,当在表达式中使用泛型类型时,SpEL 会尝试进行转换以维护它遇到的任何对象的类型正确性.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Simple {
    public List<Boolean> booleanList = new ArrayList<>();
    }

    Simple simple = new Simple();
    simple.booleanList.add(true);

    EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

    // "false" is passed in here as a String. SpEL and the conversion service
    // will recognize that it needs to be a Boolean and convert it accordingly.
    parser.parseExpression("booleanList[0]").setValue(context, simple, "false");

    // b is false
    Boolean b = simple.booleanList.get(0);

Parser Configuration

  • 可以使用解析器配置对象 ( org.springframework.expression.spel.SpelParserConfiguration ) 来配置 SpEL 表达式解析器.配置对象控制某些表达式组件的行为.

  • 例如,如果对数组或集合进行索引,并且指定索引处的元素是 null ,SpEL 可以自动创建该元素.当使用由属性引用链组成的表达式时,这非常有用.如果对数组或列表进行索引并指定超出数组或列表当前大小末尾的索引,SpEL 可以自动增长数组或列表以容纳该索引.为了在指定索引处添加元素,SpEL 将在设置指定值之前尝试使用元素类型的默认构造函数创建元素.如果元素类型没有默认构造函数, null 将被添加到数组或列表中.如果没有知道如何设置值的内置或自定义转换器, null 将保留在数组或列表中的指定索引处.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class Demo {
    public List<String> list;
    }

    // Turn on:
    // - auto null reference initialization
    // - auto collection growing
    SpelParserConfiguration config = new SpelParserConfiguration(true, true);

    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

SpEL原理及接口

  • 表达式: 表达式是表达语言的核心.用来表示"干什么"
  • 解析器: 用不将字符串表达式解析为表达式对象.用来表示"谁来干"
  • 上下文: 表达式对象执行的环境,该环境可能是定以变量,定义自定义函数,提供类型转换.用来表示"在哪干"
  • 根对象及活动上下文对象: 根对象是默认的活动上下文对象,活动上下文对象表示当前表达式操作的对象.用来表示"对谁干"