# 4.3语言引用

本节介绍Spring表达式语言的工作原理。它包括以下主题：

* 文字表达式
* 属性、数组、列表、映射和索引器
* 内联 List
* 内联 Map
* Array
* 方法
* Operators
* 类型
* Constructors
* 变量
* 功能
* bean引用
* 三元运算符（if-then-else）
* elvis
* Safe Navigation Operator

## 4.3.1 文字表达式

支持的文本表达式类型包括字符串、数值（int、real、hex）、布尔值和null。字符串由单引号分隔。要将单引号本身放入字符串中，请使用两个单引号字符。

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

```
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（）解析实数。

## 4.3.2 Properties, Arrays, Lists, Maps, and Indexers

使用属性引用导航很容易。为此，请使用句点指示嵌套的属性值。

inventor类的实例pupin和tesla使用示例部分中使用的类中列出的数据填充。为了“向下”导航并获得特斯拉的出生年份和小狗出生城市，我们使用以下表达式：

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

映射的内容是通过在括号内指定文本键值获得的。在下面的示例中，由于Officers map的键是字符串，因此我们可以指定字符串文本：

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

## 4.3.3 内联List

你可以使用符号直接在表达式中表示列表。

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

{}本身就是一个空列表。出于性能原因，如果列表本身完全由固定的文本组成，则会创建一个常量列表来表示表达式（而不是在每个计算上构建一个新列表）。

## 4.3.4 内联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的key的引号是可选的。上面的示例不使用带引号的键。

## 4.3.5 Array构造函数

可以使用熟悉的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);
```

构造多维数组时，当前无法提供初始值设定项。

## 4.3.6 方法

可以通过使用典型的Java编程语法来调用方法。还可以对文本调用方法。还支持变量参数。以下示例演示如何调用方法：

```
// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);

// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
        societyContext, Boolean.class);
```

## 4.3.7 Operators

spEL支持下面的运算符：

* 关系运算符
* 逻辑运算符
* 算数运算符
* 赋值运算符

**关系运算符**

使用标准运算符表示法支持关系运算符（等于、不等于、小于、小于或等于、大于和大于或等于）。以下列表显示了一些运算符示例：

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

> 与null的大于或小于比较遵循一个简单规则：null被视为无（不是零）。因此，任何其他值始终大于null值（x>null始终为真），任何其他值始终小于零（x < null 始终为假）。
>
> 如果你更喜欢数值比较，请避免基于数字的null比较，而是与零比较（例如，x>0或x<0）。

除了标准的关系运算符之外，spel还支持instanceof和基于正则表达式的matches运算符。下面的列表显示了这两种方法的示例：

```
// 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 (<)

gt (>)

le (<=)

ge (>=)

eq (==)

ne (!=)

div (/)

mod (%)

not (!)
```

所有的文本操作符都不区分大小写。

**逻辑操作符**

SpEL支持下面的逻辑操作符：

* and
* or
* not

下面的例子展示了如何使用logical操作符：

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

**赋值操作**

要设置属性，请使用赋值运算符（=）。这通常在对setValue的调用中完成，但也可以在对getValue的调用中完成。下面的列表显示了使用赋值运算符的两种方法：

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

## 4.3.8 类型

你可以使用特殊的T运算符来指定java.lang.class（类型）的实例。静态方法也可以使用此运算符调用。StandardEvaluationContext使用TypeLocator来查找类型，StandardTypeLocator（可以替换）是在理解java.lang包的基础上构建的。这意味着T（）对java.lang中类型的引用不需要完全限定，但所有其他类型引用都必须是限定的。下面的示例演示如何使用T运算符：

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

## 4.3.9 构造器

可以使用new运算符调用构造函数。除了基元类型（int、float等）和字符串之外，其他类型都应该使用完全限定的类名。下面的示例演示如何使用新的运算符来调用构造函数：

```
Inventor 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);
```

## 4.3.10 变量

可以使用#variableName语法引用表达式中的变量。变量是通过在EvaluationContext实现上使用setVariable方法设置的。以下示例显示如何使用变量：

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

EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");

parser.parseExpression("Name = #newName").getValue(context, tesla);
System.out.println(tesla.getName())  // "Mike Tesla"
```

**#this和#root**

\#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();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
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);
```

## 4.3.11 函数

你可以通过注册可以在表达式字符串中调用的用户定义函数来扩展spel。该函数通过EvaluationContext注册。以下示例显示如何注册用户定义函数：

```
Method method = ...;

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);
```

例如，考虑以下反转字符串的实用程序方法：

```
public abstract class StringUtils {

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

然后你可以注册并使用前面的方法，如下示例所示：

```
ExpressionParser parser = new SpelExpressionParser();

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
        StringUtils.class.getDeclaredMethod("reverseString", String.class));

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

## 4.3.12 Bean引用

如果已使用bean resolver配置了评估上下文，则可以使用@符号从表达式中查找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);
```

要访问工厂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);
```

## 4.3.13 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'";

String queryResultString = parser.parseExpression(expression)
        .getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"
```

有关三元运算符的更短语法，请参阅ELVIS运算符。

## 4.3.14 Elvis

ELVIS运算符是三元运算符语法的缩写，在groovy语言中使用。对于三元运算符语法，通常必须重复变量两次，如下示例所示：

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

相反，你可以使用Elvis操作符（以Elvis的发型命名）。下面的示例演示如何使用Elvis运算符：

```
ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("name?:'Unknown'").getValue(String.class);
System.out.println(name);  // 'Unknown'
```

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

```
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Nikola Tesla

tesla.setName(null);
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Elvis Presley
```

> 可以使用ELVIS运算符在表达式中应用默认值。下面的示例演示如何在@value表达式中使用elvis运算符：
>
> @Value("#{systemProperties\['pop3.port'] ?: 25}")
>
> 如果定义了系统属性pop3.port，则会注入该属性，否则会注入25。

## 4.3.15 Safe Navigation 运算符

Safe Navigation操作符用于避免nullpointerException，它来自groovy语言。通常，当你引用一个对象时，你可能需要在访问该对象的方法或属性之前验证它不是空的。为了避免这种情况，Safe Navigation操作符返回空值而不是抛出异常。以下示例说明如何使用Safe Navigation：

```
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!!!
```

## 4.3.16 集合选择

Selection是一种功能强大的表达式语言功能，通过从源集合的条目中进行选择，可以将源集合转换为另一个集合。

Selection使用的语法为.？\[selectionExpression]。它过滤集合并返回包含原始元素子集的新集合。例如，selection可以让我们很容易地获得塞尔维亚发明家的列表，如下示例所示：

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

在list和map上都可以Selection。对于list，将根据每个单独的列表元素评估选择条件。针对map，选择标准针对每个映射条目（Java类型Map.Entry）进行评估。每个map项都有其键和值，可以作为属性访问，以便在选择中使用。

以下表达式返回一个新map，该映射由原始map的那些元素组成，其中输入值小于27：

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

除了返回所有选定的元素之外，你还能检索第一个或最后一个值。要获取与所选内容匹配的第一个条目，语法为。.^\[selectionExpression]。要获取最后一个匹配的选择，语法为.$\[SelectionExpression]。

## 4.3.17 集合投影

Projection允许集合驱动子表达式的计算，结果是一个新集合。投影的语法是.!\[projectionExpression]。例如，假设我们有一个发明家列表，但是想要他们出生的城市列表。实际上，我们想为发明家列表中的每个条目评估“placeofbirth.city”。下面的示例使用投影进行此操作：

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

你还可以使用map来驱动投影，在这种情况下，投影表达式针对map中的每个条目（表示为Java Map.Entry）进行评估。跨map投影的结果是一个列表，其中包含对每个map条目的投影表达式的计算。

## 4.3.18 表达式模板化

表达式模板允许将文本与一个或多个计算块混合。每个评估块都由你可以定义的前缀和后缀字符分隔。常见的选择是使用#{ }作为分隔符，如下示例所示：

```
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;
    }
}
```
