本节描述了 Spring 表达式语言的工作方式。它包括以下主题:

  • 字面量表达式
  • Properties, Arrays, Lists, Maps, 和 Indexers(索引器)
  • Inline Lists / 行内 List
  • Inline Maps / 行内 Map
  • Array/数组 构造
  • Methods / 方法
  • 运算符
  • Types / 类型
  • 构造函数
  • Variables / 变量
  • Functions / 函数
  • Bean 引用
  • 三元运算符
  • Elvis(埃尔维斯)运算符
  • 安全导航操作符
  • 集合选择
  • Collection Projection / 集合投影
  • 模板表达式

    字面量表达式

支持的字面表达式类型有:字符串、数字值(int、real、hex)、布尔值和 null。字符串以单引号为界。要把单引号本身放在一个字符串中,请使用两个单引号字符。

下面的列表显示了字词的简单用法。通常情况下,它们不会像这样单独使用,而是作为更复杂的表达式的一部分—例如,在逻辑比较运算符的一侧使用字符串。

  1. ExpressionParser parser = new SpelExpressionParser();
  2. // evals to "Hello World"
  3. String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();
  4. double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();
  5. // evals to 2147483647
  6. int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();
  7. boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
  8. Object nullValue = parser.parseExpression("null").getValue();

数字支持使用负号、指数符号和小数点。默认情况下,实数是通过使用 Double.parseDouble() 来解析的。

Properties, Arrays, Lists, Maps, 和 Indexers(索引器)

用属性引用进行导航很容易。要做到这一点,使用一个句号来表示一个嵌套的属性值。Inventor 类的实例,pupin 和 tesla,是用例子中使用的类中列出的数据填充的。为了 「向下」 浏览对象图并获得 Tesla’s 的出生年份和 pupin 的出生城市,我们使用以下表达式:

  1. // evals to 1856
  2. int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);
  3. String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);

:::info 对于属性名称的第一个字母,允许不区分大小写。因此,上例中的表达式可以分别写成 Birthdate.Year + 1900PlaceOfBirth.City。此外,属性可以选择通过方法调用来访问—例如,getPlaceOfBirth().getCity()而不是placeOfBirth.city。 :::

数组和列表的内容是通过使用方括号符号获得的,如下例所示:

  1. ExpressionParser parser = new SpelExpressionParser();
  2. EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
  3. // Inventions Array
  4. // evaluates to "Induction motor"; 获取数组中的第 4 个元素
  5. String invention = parser.parseExpression("inventions[3]").getValue(
  6. context, tesla, String.class);
  7. // Members List
  8. // evaluates to "Nikola Tesla"; 获取列表中的第 1 个元素
  9. String name = parser.parseExpression("members[0].name").getValue(
  10. context, ieee, String.class);
  11. // List and Array navigation; 列表 和 数组导航
  12. // evaluates to "Wireless communication"
  13. String invention = parser.parseExpression("members[0].inventions[6]").getValue(
  14. context, ieee, String.class);

Map 的内容是通过在括号内指定字面量的键值来获得的。在下面的例子中,由于 officers map 的键是字符串,我们可以指定字符串字面量:

  1. // officers 是个 Map 数据结构,键值对的方式;注意方括号中的字符串,前面说了是单引号引起来的
  2. Inventor pupin = parser.parseExpression("officers['president']").getValue(
  3. societyContext, Inventor.class);
  4. // evaluates to "Idvor"
  5. String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
  6. societyContext, String.class);
  7. // setting values
  8. parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
  9. societyContext, "Croatia");

Inline Lists / 行内 List

你可以通过使用 {}符号直接在表达式中表达 List:

  1. // 评估为一个包含四个数字的 Java List
  2. List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);
  3. List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);

{}本身意味着一个空 List 。出于性能方面的考虑,如果 List 本身完全由固定字词组成,那么将创建一个常量 List 来表示该表达式(而不是在每次评估时建立一个新的 List)。

Inline Maps / 行内 Map

你也可以通过使用 {key:value}符号在表达式中直接表达 Map。下面的例子展示了如何做到这一点:

  1. // 评估一个包含 2 个项目的 Java Map
  2. Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);
  3. Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);

{:}本身意味着一个空映射。出于性能方面的考虑,如果 Map 本身是由固定字面或其他嵌套的常量结构( List 或 Map)组成的,那么将创建一个常量 Map 来表示该表达式(而不是在每次评估时建立一个新的 Map)。Map 键的引号是可选的(除非键包含一个句号(.))。上面的例子没有使用带引号的键。

Array/数组 构造

你可以使用熟悉的 Java 语法来建立数组,可以选择提供一个初始化器,以便在构造时填充数组。下面的例子显示了如何做到这一点:

  1. int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);
  2. // Array 使用了初始化器
  3. int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);
  4. // 多维数组
  5. int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);

Methods / 方法

你可以通过使用典型的 Java 编程语法来调用方法。你也可以在字面上调用方法。也支持变量参数。下面的例子展示了如何调用方法:

  1. // 字符串,评估结果为 "bc"
  2. String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);
  3. // evaluates to true
  4. boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
  5. societyContext, Boolean.class);

运算符

Spring 表达式语言支持以下种类的运算符:

  • 关系运算符
  • 逻辑运算符
  • 数学运算符
  • 赋值运算符

关系运算符

关系运算符(等于、不等于、小于、小于或等于、大于、大于或等于)通过使用标准运算符符号来支持。下面列出了一些运算符的例子:

  1. // evaluates to true
  2. boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);
  3. // evaluates to false
  4. boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);
  5. // evaluates to true
  6. boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);

:::info 针对 null 的大于和小于比较遵循一个简单的规则:null 被视为无(即不是零)。因此,任何其他的值总是大于 null(X>null 总是真),没有其他的值会小于 nothing(X如果你喜欢数字比较,则应避免基于数字的空值比较,而采用与零的比较(例如,X>0 或 X<0)。 :::

除了标准的关系运算符之外,SpEL 还支持 instanceof 和基于正则表达式的匹配运算符。下面的列表显示了这两者的例子:

  1. // evaluates to false
  2. boolean falseValue = parser.parseExpression(
  3. "'xyz' instanceof T(Integer)").getValue(Boolean.class);
  4. // evaluates to true
  5. boolean trueValue = parser.parseExpression(
  6. "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);
  7. // evaluates to false
  8. boolean falseValue = parser.parseExpression(
  9. "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

:::danger 对原始类型要小心,因为它们会立即被框在它们的封装类型上。例如,1 instanceof T(int) 的值为 false,而 1 instanceof T(Integer) 的值为true,正如预期的那样。 :::

每个符号运算符也可以被指定为纯字母的等价物。这就避免了所使用的符号对于表达式所嵌入的文档类型具有特殊意义的问题(比如在 XML 文档中)。文本等价物是:

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

逻辑运算符

SpEL 支持以下的逻辑运算符

  • and (&&)
  • or (|)
  • not (!)

下面的例子显示了如何使用逻辑运算符:

  1. // -- AND --
  2. // evaluates to false
  3. boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);
  4. // evaluates to true
  5. String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
  6. boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
  7. // -- OR --
  8. // evaluates to true
  9. boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);
  10. // evaluates to true
  11. String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
  12. boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
  13. // -- NOT --
  14. // evaluates to false
  15. boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
  16. // -- AND and NOT --
  17. String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
  18. boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

数学运算符

你可以在数字和字符串上使用加法运算符(+)。你只能在数字上使用减法(-),乘法(*),和除法(/)运算符。你也可以在数字上使用模数(%)和指数幂(^)运算符。标准的运算符优先级是强制执行的。下面的例子显示了正在使用的数学运算符:

  1. // 加法
  2. int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2
  3. String testString = parser.parseExpression(
  4. "'test' + ' ' + 'string'").getValue(String.class); // 'test string'
  5. // 减法
  6. int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4
  7. double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000
  8. // 乘法
  9. int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6
  10. double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0
  11. // 除法
  12. int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2
  13. double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0
  14. // 取模
  15. int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3
  16. int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1
  17. // 运算符的优先级
  18. int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21

赋值运算符

要设置一个属性,使用赋值运算符(=)。这通常是在调用 setValue时进行,但也可以在调用 getValue时进行。下面的列表显示了使用赋值运算符的两种方式:

  1. Inventor inventor = new Inventor();
  2. EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
  3. parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic");
  4. // 或则
  5. // 这个稍微有点难理解,先说返回的结果就是 Aleksandar Seovic
  6. // 如果你的表达式为 "name = 'Aleksandar Seovic2'" 那么返回的结果就是 Aleksandar Seovic2, 并且 inventor 中的 name=Aleksandar Seovic2
  7. // 或许可以尝试着这样理解:这个是评估表达式,所以并不能使用这里的 API get/set 来理解
  8. // 表达式是 将 name=xxx,在评估是,就会在 跟 对象中去执行这个表达式,就完成了赋值,然后返回了 name 的值
  9. String aleks = parser.parseExpression(
  10. "name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);

Types / 类型

你可以使用特殊的 T 操作符来指定一个 java.lang.Class(type)的实例。静态方法 也是通过使用这个操作符来调用的。StandardEvaluationContext使用 TypeLocator来寻找类型,而 StandardTypeLocator(可以被替换)是在了解 java.lang包的情况下建立的。这意味着对 java.lang包内类型的 T()引用不需要完全限定,但所有其他类型的引用必须限定。下面的例子展示了如何使用 T 操作符:

  1. // 返回的是 Class 的实例,而不是这个类的实例,一定要注意
  2. Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
  3. Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
  4. // 这里调用了的静态常量
  5. boolean trueValue = parser.parseExpression(
  6. "T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
  7. .getValue(Boolean.class);

构造函数

你可以通过使用 new操作符来调用构造函数。除了位于 java.lang包中的类型(Integer、Float、String 等),你应该对所有类型使用完全合格的类名。下面的例子展示了如何使用 new操作符来调用构造函数:

  1. package cn.mrcode.study.springdocsread.data;
  2. import org.springframework.expression.ExpressionParser;
  3. import org.springframework.expression.spel.standard.SpelExpressionParser;
  4. import java.util.ArrayList;
  5. import java.util.List;
  6. /**
  7. * @author mrcode
  8. */
  9. public class DemoTest {
  10. public static void main(String[] args) {
  11. ExpressionParser parser = new SpelExpressionParser();
  12. Inventor einstein = parser.parseExpression(
  13. "new cn.mrcode.study.springdocsread.data.Inventor('Albert Einstein', 'German')")
  14. .getValue(Inventor.class);
  15. // 在 list 的 add 方法中创建一个 inventor 的实例
  16. final Demo societyContext = new Demo();
  17. parser.parseExpression("Members.add(new cn.mrcode.study.springdocsread.data.Inventor('Albert Einstein', 'German'))")
  18. .getValue(societyContext);
  19. System.out.println(societyContext);
  20. }
  21. static class Demo {
  22. public List<Inventor> members = new ArrayList<>();
  23. }
  24. }

需要注意的是这个 Members,笔者以为是一个什么内置的方法,应该就是一个成员变量,如上述代码所示;

Variables / 变量

你可以通过使用 #variableName语法在表达式中引用变量。变量是通过使用 EvaluationContext实现上的 setVariable方法来设置的。

:::info 有效的变量名称必须由以下一个或多个支持的字符组成:

  • 字母 A - Z 和 a - z
  • 数字 0 - 9
  • 下划线 _
  • 美元符合 $ :::

下面的例子显示了如何使用变量:

  1. Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
  2. EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
  3. // 在 评估上下文中,设置一个变量
  4. context.setVariable("newName", "Mike Tesla");
  5. // 在表达式中使用这个变量赋值
  6. parser.parseExpression("name = #newName").getValue(context, tesla);
  7. System.out.println(tesla.getName()) // "Mike Tesla"

#this 和 #root 变量

#this变量总是被定义的,它指的是当前的评估对象(不合格的引用会被解决)。#root变量总是被定义的,它指的是根上下文对象。尽管 #this可能会随着表达式的组成部分被评估而变化,但是 #root总是指的是根。下面的例子展示了如何使用 #this#root变量:

  1. // 创建一个整数的数组
  2. List<Integer> primes = new ArrayList<Integer>();
  3. primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
  4. // 创建解析器
  5. ExpressionParser parser = new SpelExpressionParser();
  6. EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
  7. // 将数组设置为 primes 变量
  8. context.setVariable("primes", primes);
  9. // 所有大于 10 的数,从列表中选择(使用选择 ?{...})。
  10. // 评估结果为: [11, 13, 17]
  11. List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
  12. "#primes.?[#this>10]").getValue(context);

Functions / 函数

你可以通过注册可在表达式字符串中调用的用户定义的函数来扩展 SpEL。该函数是通过 EvaluationContext 注册的。下面的例子显示了如何注册一个用户定义的函数:

  1. Method method = ...;
  2. EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
  3. context.setVariable("myFunction", method);

例如,考虑下面的静态方法,它可以反转一个字符串:

  1. public abstract class StringUtils {
  2. public static String reverseString(String input) {
  3. StringBuilder backwards = new StringBuilder(input.length());
  4. for (int i = 0; i < input.length(); i++) {
  5. backwards.append(input.charAt(input.length() - 1 - i));
  6. }
  7. return backwards.toString();
  8. }
  9. }

然后注册并使用它:

  1. ExpressionParser parser = new SpelExpressionParser();
  2. EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
  3. context.setVariable("reverseString",
  4. // 获取到 Method 方法
  5. StringUtils.class.getDeclaredMethod("reverseString", String.class));
  6. // 结果 olleh
  7. String helloWorldReversed = parser.parseExpression(
  8. "#reverseString('hello')").getValue(context, String.class);

Bean 引用

如果评估上下文已经被配置了 Bean 解析器,你可以通过使用 @符号从表达式中查找 Bean。下面的例子展示了如何做到这一点:

  1. package cn.mrcode.study.springdocsread.data;
  2. import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  3. import org.springframework.context.expression.BeanFactoryResolver;
  4. import org.springframework.expression.EvaluationContext;
  5. import org.springframework.expression.ExpressionParser;
  6. import org.springframework.expression.spel.standard.SpelExpressionParser;
  7. import org.springframework.expression.spel.support.SimpleEvaluationContext;
  8. import org.springframework.expression.spel.support.StandardEvaluationContext;
  9. import java.util.ArrayList;
  10. import java.util.List;
  11. import cn.mrcode.study.springdocsread.web.AppConfig;
  12. /**
  13. * @author mrcode
  14. */
  15. public class DemoTest {
  16. public static void main(String[] args) throws NoSuchMethodException {
  17. final AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
  18. ExpressionParser parser = new SpelExpressionParser();
  19. StandardEvaluationContext context = new StandardEvaluationContext();
  20. // 解析中中放入的就是一个 IOC 容器
  21. context.setBeanResolver(new BeanFactoryResolver(ctx));
  22. // 这将导致在评估过程中调用 MyBeanResolver 的 resolve(context, "something")。
  23. // 这里将获取到 容器中的 demoService
  24. Object bean = parser.parseExpression("@demoService").getValue(context);
  25. }
  26. }

要访问工厂 bean 本身,您应该在 bean 名称前加上 & 符号。以下示例显示了如何执行此操作:

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

这个获取的是什么,我忘记了,可以参考这个文章 使用 FactoryBean 定制实例化逻辑(不过这个里面的介绍也很端,这可能需要看看 FactoryBean 在哪里用的才能明白了),

三元运算符

你可以使用三元操作符在表达式内部执行 if-then-else条件逻辑。下面的列表显示了一个最小的例子:

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

在这种情况下,布尔值 false 的结果是返回字符串值 ‘falseExp’。下面是一个更现实的例子:

  1. parser.parseExpression("name").setValue(societyContext, "IEEE");
  2. // 设置了一个变量
  3. societyContext.setVariable("queryName", "Nikola Tesla");
  4. // 这里使用了 根对象的 isMember 方法,传入的参数是上面定义的变量
  5. String expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
  6. "+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";
  7. String queryResultString = parser.parseExpression(expression)
  8. .getValue(societyContext, String.class);
  9. // queryResultString = "Nikola Tesla is a member of the IEEE Society"

Elvis(埃尔维斯)运算符

elvis 运算符是三元运算符语法的缩短版,在 Groovy 语言中使用。在三元运算符语法中,你通常要将一个变量重复两次,正如下面的例子所示:

  1. String name = "Elvis Presley";
  2. String displayName = (name != null ? name : "Unknown");

相反,你可以使用 elvis 运算符(因与埃尔维斯的发型相似而命名)。下面的例子显示了如何使用:

  1. ExpressionParser parser = new SpelExpressionParser();
  2. String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
  3. System.out.println(name); // 'Unknown'

下面的列表显示了一个更复杂的例子:

  1. ExpressionParser parser = new SpelExpressionParser();
  2. EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
  3. Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
  4. // name 不存在的话,这返回这个默认值,否则返回 name 的值
  5. String name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
  6. System.out.println(name); // Nikola Tesla
  7. tesla.setName(null);
  8. name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
  9. System.out.println(name); // Elvis Presley

我们可以使 elvis 运算符来在表达式中应用默认值。下面的例子显示了如何在 @Value表达式中使用 Elvis 运算符:

  1. @Value("#{systemProperties['pop3.port'] ?: 25}")

这将注入一个系统属性 pop3.port(如果它被定义了),如果没有则注入 25。

安全导航操作符

安全导航操作符是用来避免 NullPointerException 的,来自 Groovy 语言。通常,当你有一个对象的引用时,你可能需要在访问该对象的方法或属性之前验证它是否为空。为了避免这种情况,安全导航操作符返回 null,而不是抛出一个异常。下面的例子显示了如何使用安全导航操作符:

  1. ExpressionParser parser = new SpelExpressionParser();
  2. EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
  3. Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
  4. tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));
  5. String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
  6. System.out.println(city); // Smiljan
  7. // 设置这个为 null
  8. tesla.setPlaceOfBirth(null);
  9. // 想要从一个 null 上获取属性 city,这里会直接返回 null 而不是抛出一个 NPE 异常
  10. city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class);
  11. System.out.println(city); // null - does not throw NullPointerException!!!

:::tips 需要注意的是:这里测试 map 这种取值无法使用安全导航操作符来避免 map 变量为 null :::

集合选择

选择是一个强大的表达式语言功能,它让你通过从一个源集合的条目中进行选择,将其转化为另一个集合。

选择使用的语法是 .? [selectionExpression]。它对集合进行过滤,并返回一个新的集合,其中包含原始元素的一个子集。例如,选择让我们很容易得到一个塞尔维亚发明家的列表,如下例所示:

  1. // 注意这个 members,是 societyContext 这个根对象里面的一个属性
  2. // 这个属性是一个 list,含义是:将 members 中对象的 nationality 属性 = Serbian 的过滤出来
  3. List<Inventor> list = (List<Inventor>) parser.parseExpression(
  4. "members.?[nationality == 'Serbian']").getValue(societyContext);
  5. // 这个也是可以配合安全导航操作符来使用
  6. List<Inventor> list = (List<Inventor>) parser.parseExpression(
  7. "members?.?[nationality == 'Serbian']").getValue(societyContext);

对于数组和任何实现 java.lang.Iterablejava.util.Map的东西,都支持选择。对于一个列表或数组,选择标准是针对每个单独的元素进行评估的。对于一个 Map,选择标准是针对每个 Map 条目(Java 类型 Map.Entry 的对象)进行评估的。每个 Map 条目都有它的键和值,可以作为属性在选择中使用。

下面的表达式返回一个新的 Map,该 Map 由原 Map 中那些条目值小于 27 的元素组成:

  1. package cn.mrcode.study.springdocsread.data;
  2. import org.springframework.expression.ExpressionParser;
  3. import org.springframework.expression.spel.standard.SpelExpressionParser;
  4. import java.util.HashMap;
  5. import java.util.Map;
  6. /**
  7. * @author mrcode
  8. */
  9. public class DemoTest {
  10. public static void main(String[] args) throws NoSuchMethodException {
  11. ExpressionParser parser = new SpelExpressionParser();
  12. final Demo demo = new Demo();
  13. demo.map.put("a", 1);
  14. demo.map.put("b", 28);
  15. // 返回了 a,1 这一条数据
  16. Map newMap = (Map) parser.parseExpression("map.?[value<27]").getValue(demo);
  17. System.out.println();
  18. }
  19. static class Demo {
  20. public Map<String, Integer> map = new HashMap<>();
  21. }
  22. }

除了返回所有选择的元素外,你还可以只检索第一个或最后一个元素:

  • 要获得与选择匹配的第一个元素,语法是 .^[selectionExpression]
  • 要获得最后一个匹配的选择,其语法是 .$[selectionExpression]

    Collection Projection / 集合投影

投射让一个集合驱动一个子表达式的评估,其结果是一个新的集合。投射的语法是 .![projectionExpression]。例如,假设我们有一个发明家的列表,但想要他们出生地的城市列表。实际上,我们想对发明家列表中的每一个条目评估 "placeOfBirth.city"。下面的例子使用了投影来实现这个目的。

  1. package cn.mrcode.study.springdocsread.data;
  2. import org.springframework.expression.ExpressionParser;
  3. import org.springframework.expression.spel.standard.SpelExpressionParser;
  4. import java.util.ArrayList;
  5. import java.util.List;
  6. /**
  7. * @author mrcode
  8. */
  9. public class DemoTest {
  10. public static void main(String[] args) throws NoSuchMethodException {
  11. // 构造数据
  12. final Demo demo = new Demo();
  13. demo.members.add(new Persion("Smiljan"));
  14. demo.members.add(new Persion("Idvor"));
  15. ExpressionParser parser = new SpelExpressionParser();
  16. // returns ['Smiljan', 'Idvor' ]
  17. List placesOfBirth = (List) parser.parseExpression("members.![city]").getValue(demo);
  18. System.out.println();
  19. }
  20. static class Demo {
  21. public List<Persion> members = new ArrayList<>();
  22. }
  23. static class Persion {
  24. public String city;
  25. public Persion(String city) {
  26. this.city = city;
  27. }
  28. }
  29. }

模板表达式

表达式模板允许将字面量文本与一个或多个评价块混合。每个评估块都有前缀和后缀,你可以定义这些字符。一个常见的选择是使用 #{ }作为分隔符,如下面的例子所示:

  1. ExpressionParser parser = new SpelExpressionParser();
  2. String randomPhrase = parser.parseExpression(
  3. // 引用了 Math.random() 静态方法
  4. "random number is #{T(java.lang.Math).random()}",
  5. new TemplateParserContext()).getValue(String.class);
  6. // evaluates to "random number is 0.7038186818312008"

该字符串的评估方法是将字面文本 random number is#{ }分隔符内的表达式的评估结果(在本例中是调用 ramdom()方法的结果)连接起来。parseExpression()方法的第二个参数是 ParserContext 类型。ParserContext 接口被用来影响表达式的解析方式,以支持表达式模板化功能。TemplateParserContext 的定义如下:

  1. public class TemplateParserContext implements ParserContext {
  2. public String getExpressionPrefix() {
  3. return "#{";
  4. }
  5. public String getExpressionSuffix() {
  6. return "}";
  7. }
  8. public boolean isTemplate() {
  9. return true;
  10. }
  11. }