6.5 语言参考

6.5.1 字符表达式

字符串表达式类型支持strings,数值类型 (int, real, hex),布尔和null.strings值通过单引号引用。如果字符串里面又包含字符串,通过双引号引用。

下面列出了字符串表达式的常用例子。通常它们不会被单独使用,而是结合一个更复杂的表达式一起使用,例如,在逻辑比较运算符中使用表达式:

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

数字类型支持负数,指数和小数点。默认情况下实数会使用Double.parseDouble()解析。

6.5.2 属性, 数组, 列表, Maps, 索引

属性引用比较简单:只需要用点号(.)标识级联的各个属性值。Inventor类的实例:pupin,和tesla,所用到的数据在Classes used in the examples一节有列出。
下面的表达式示例用来解析Tesla的出生年及Pupin的出生城市。

// evals to 1856
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);

String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);

属性名的第一个字母可以是大小写敏感的。数组和列表的内容可以使用方括号来标记

ExpressionParser parser = new SpelExpressionParser();

// Inventions Array
StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);

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

// Members List
StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);

// evaluates to "Nikola Tesla"
String name = parser.parseExpression("Members[0].Name").getValue(
        societyContext, String.class);

// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
        societyContext, String.class);

Maps的值由方括号内指定字符串的Key来标识引用。在下面这个例子中,因为Officers map的Key是string类型,我们可以用过字符串常量指定。

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

6.5.3 内联列表

列表(Lists)可以用过大括号{}直接引用

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

{}本身代表一个空list.因为性能的关系,如果列表本身完全由固定的常量值组成,这个时候会创建一个常量列表来代替表达式,而不是每次在求值的时候创建一个新的列表。

6.5.4 内联Maps

Maps也可以直接通过{key:value}标记的方式在表达式中使用

// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);

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

{:} 本身代表一个空的Map。因为性能的原因:如果Map本身包含固定的常量或者其他级联的常量结构(lists或者maps)则一个常量Map会创建来代表表达式,而不是每次求值的时候都创建一个新的Map.Map的Key并不一定需要引号引用、比如上面的例子就没有引用。

6.5.5 创建数组

数组可以使用类似于Java的语法创建,创建时可以事先指定数组的容量大小、这个是可选的。

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

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

// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);

在创建多维数组时还不支持事先指定初始化的值。

6.5.6 方法

方法可以使用典型的Java编程语法来调用。方法可以直接在字符串常量上调用。可变参数也是支持的。

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

6.5.7 运算符

关系运算符

关系运算符;包括==,<>,<,<=,>,>=等标准运算符都是可以直接支持的

// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);

// evaluates to false
boolean falseValue = parser.parseExpression("2 &amp;lt; -5.0").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression("'black' &amp;lt; 'block'").getValue(Boolean.class);

Note:>/<运算符和null做比较时遵循一个简单的规则:null代表什么都没有(不代表0).因此,所有的值总是大于null(X>null总是true)
也就是没有一个值会小于什么都没有(x<null总是返回false).尽量不要在数值比较中使用null,而是和0做比较(例如X>0或者X<0).

除了标准的关系运算符,SpEL还支持instanceof关键字和基于matches操作符的正则表达式。

备注:需要注意元数据类型会自动装箱成包装类型,因此1 instanceof T(int)结果是false,1 instanceof T(Integer)的结果是true.

每一个符号操作符也可以通过首字母简写的方式标识。这样可以避免表达式所用的符号在当前表达式所在的文档中存在特殊含义而带来的冲突(比如XML文档的<).简写的符号有:lt (<), gt (>), le (?), ge (>=), eq (==), ne (!=), div (/), mod (%), not (!). 这里不区分大小写。

逻辑运算符

支持的逻辑运算符有:and,or,和not.它们的使用方法如下:

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

算术运算符

加号运算符可以同时用于数字和字符串。减号,乘法和除法只能用于数字。其他支持的算术运算符有取模(%)和指数(^).遵循标准运算符优先级。下面是一些例子:

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

6.5.8 赋值

属性可以使用赋值运算符设置。可以通过调用setValue设置。但是也可以在getValue方法中设置

Inventor inventor = new Inventor();
StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor);

parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2");

// alternatively

String aleks = parser.parseExpression(
        "Name = 'Alexandar Seovic'").getValue(inventorContext, String.class);

6.5.9 类型

T操作符是一个特殊的操作符、可以同于指定java.lang.Class的实例(类型)。静态方法也可以通过这个操作符调用。

StandardEvaluationContext使用TypeLocator来查找类型,其中StandardTypeLocator(这个可以被替换使用其他类)默认对java.lang包里的类型可见。也就是说 T()引用java.lang包里面的类型不需要限定包全名,但是其他类型的引用必须要。

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 &amp;lt; T(java.math.RoundingMode).FLOOR")
        .getValue(Boolean.class);

6.5.10 构造器

构造器可以使用new操作符来调用。除了元数据类型和String(比如int,float等可以直接使用)都需要限定类的全名。

nventor einstein = p.parseExpression(
        "new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
        .getValue(Inventor.class);

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

6.5.11 变量

表达式中的变量可以通过语法#变量名使用。变量可以在StandardEvaluationContext中通过方法setVariable设置。

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
context.setVariable("newName", "Mike Tesla");

parser.parseExpression("Name = #newName").getValue(context);

System.out.println(tesla.getName()) // "Mike Tesla"

#this和#root变量

#this变量永远指向当前表达式正在求值的对象(这时不需要限定全名)。变量#root总是指向根上下文对象。#this在表达式不同部分解析过程中可能会改变,但是#root总是指向根

// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));

// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("primes",primes);

// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
        "#primes.?[#this>10]").getValue(context);

6.5.12 函数

你可以扩展SpEL,在表达式字符串中使用自定义函数。这些自定义函数是通过StandardEvaluationContext的registerFunction来注册的

public void registerFunction(String name, Method m)

首先定义一个Java方法作为函数的实现、例如下面是一个将字符串反转的方法。

public abstract class StringUtils {

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

然后将这个方法注册到求值上下文中就可以应用到表达式字符串中。

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();

context.registerFunction("reverseString",
    StringUtils.class.getDeclaredMethod("reverseString", new Class[] { String.class }));

String helloWorldReversed = parser.parseExpression(
    "#reverseString('hello')").getValue(context, String.class);

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

如果是访问工厂Bean,bean名字前需要添加前缀(&)符号

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

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

6.5.14 三元操作符 (If-Then-Else)

你可以在表达式中使用三元操作符来实现if-then-else的条件逻辑。下面是一个小例子:

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

在这个例子中,因为布尔值false返回的结果一定是’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'";

6.5.15 Elvis运算符

Elvis运算符可以简化Java的三元操作符,是Groovy中使用的一种操作符。如果使用三元操作符语法你通常需要重复写两次变量名,例如:

String name = "Elvis Presley";
String displayName = name != null ? name : "Unknown";

使用Elvis运算符可以简化写法,这个符号的名字由来是它很像Elvis的发型(译者注:Elvis=Elvis Presley,猫王,著名摇滚歌手)

ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("name?:'Unknown'").getValue(String.class);

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

tesla.setName(null);

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

System.out.println(name); // Elvis Presley

6.5.16 安全引用运算符

安全引用运算符主要为了避免空指针,源于Groovy语言。很多时候你引用一个对象的方法或者属性时都需要做非空校验。为了避免此类问题、使用安全引用运算符只会返回null而不是抛出一个异常。

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

tesla.setPlaceOfBirth(null);

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

System.out.println(city); // null - does not throw NullPointerException!!!

备注:Elvis操作符可以在表达式中赋默认值,例如。在一个@Value表达式中:@Value(“#{systemProperties[‘pop3.port’] ?: 25}”)
上面的例子如果系统属性pop3.port已定义会直接注入,如果未定义,则返回默认值25.

6.5.17 集合筛选

该功能是SpEL中一项强大的语言特性,允许你将源集合选择其中的某几项生成另外一个集合。选择器使用语法.?[selectionExpression].通过该表达式可以过滤集合并返回原集合中的子集。例如,下面的例子我们返回inventors对象中的国籍为塞尔维亚的子集:

List<Inventor> list = (List<Inventor>) parser.parseExpression(
        "Members.?[Nationality == 'Serbian']").getValue(societyContext);

筛选可以同时在list和maps上面使用。对于list来说是选择的标准是针对单个列表的每一项来比对求值,对于map来说选择的标准是针对Map的每一项(类型为Java的Map.Entry)。Map项的Key和alue都可以作为筛选的比较选项

下面的例子中表达式会返回一个新的map,包含原map中值小于27的所有子项。

Map newMap = parser.parseExpression("map.?[value<27]").getValue();

除了可以返回所有被选择的元素,也可以只返回第一或者最后一项。返回第一项的选择语法是:

^[…​],返回最后一项的选择语法是 $[…​].

6.5.18 集合投影

投影使得一个集合通过子表达式求值,并返回一个新的结果。投影的语法是 ![projectionExpression]. 举一个通俗易懂的例子,假设我们有一个inventors 对象列表,但是我们想返回每一个inventor出生的城市列表。我们需要遍历inventor的每一项,通过 ‘placeOfBirth.city’来求值。下面是具体的代码例子:

// returns ['Smiljan', 'Idvor' ]
List placesOfBirth = (List)parser.parseExpression("Members.![placeOfBirth.city]");

也可以在Map上使用投影、在这种场景下投影表达式会作用于Map的每一项(类型为Java的Map.Entry)。Map项的Key和alue都可以作为选择器的比较选项Map投影的结果是一个list,包含map每一项被投影表达式求值后的结果。

6.5.19 表达式模板

表达式模板运行在一段文本中混合包含一个或多个求值表达式模块。各个求值块都通过可被自定义的前后缀字符分隔,一个通用的选择是使用#{ }作为分隔符。例如:

String randomPhrase = parser.parseExpression(
        "random number is #{T(java.lang.Math).random()}",
        new TemplateParserContext()).getValue(String.class);

// evaluates to "random number is 0.7038186818312008"

求值的字符串是通过字符文本’random number is’以及#{}分隔符中的表达式求值结果拼接起来的,在这个例子中就是调用random()的结果。方法parseExpression()的第二个传入参数类型是ParserContext。ParserContext接口用来确定表达式该如何被解析、从而支持表达式的模板功能。其实现类TemplateParserContext的定义如下:

public class TemplateParserContext implements ParserContext {

    public String getExpressionPrefix() {
        return "#{";
    }

    public String getExpressionSuffix() {
        return "}";
    }

    public boolean isTemplate() {
        return true;
    }
}