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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flydean.com/spring-framework-documentation5/core-technologies/4.spel/4.3language-reference.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
