Properties, Arrays, Lists, Maps, and Indexers

Spring 表达式语言支持在对象图中导航并索引到各种结构中。

The Spring Expression Language provides support for navigating object graphs and indexing into various structures.

数值索引值从零开始,例如在 Java 中访问数组中的第 nth 个元素时。

Numerical index values are zero-based, such as when accessing the nth element of an array in Java.

有关如何使用空安全运算符浏览对象图,并对各种结构进行索引的详细信息,请参阅 Safe Navigation Operator 部分。

See the Safe Navigation Operator section for details on how to navigate object graphs and index into various structures using the null-safe operator.

Property Navigation

您可以通过使用句点指示嵌套属性值来导航对象图中的属性引用。Inventor 类、pupintesla 的实例已使用已在 Classes used in the examples 部分中列出的数据填充。通过导航 down 对象图并获取特斯拉的出生年份和普平的出生城市,我们使用以下表达式:

You can navigate property references within an object graph by using a period to indicate a nested property value. The instances of the Inventor class, pupin and tesla, were populated with data listed in the Classes used in the examples section. To navigate down the object graph and get Tesla’s year of birth and Pupin’s city of birth, we use the following expressions:

  • Java

  • Kotlin

// evaluates to 1856
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);

// evaluates to "Smiljan"
String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
// evaluates to 1856
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int

// evaluates to "Smiljan"
val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String

属性名称的首字母可以不区分大小写。 因此,上面的示例中的表达式可以分别写成 Birthdate.Year + 1900PlaceOfBirth.City。 此外,还可以通过方法调用访问属性,例如,getPlaceOfBirth().getCity() 代替 placeOfBirth.city

Case insensitivity is allowed for the first letter of property names. Thus, the expressions in the above example may be written as Birthdate.Year + 1900 and PlaceOfBirth.City, respectively. In addition, properties may optionally be accessed via method invocations — for example, getPlaceOfBirth().getCity() instead of placeOfBirth.city.

Indexing into Arrays and Collections

数组或集合(例如,Set 或 List)的第 n 个元素可以通过使用方括号表示法获取,如下面的示例所示。

The nth element of an array or collection (for example, a Set or List) can be obtained by using square bracket notation, as the following example shows.

如果索引集合是 java.util.List,则可以通过 list.get(n) 直接访问第 n 个元素。

If the indexed collection is a java.util.List, the nth element will be accessed directly via list.get(n).

对于任何其他类型的 Collection,将通过使用其 Iterator 遍历集合并返回遇到的第 n 个元素来访问第 n 个元素。

For any other type of Collection, the nth element will be accessed by iterating over the collection using its Iterator and returning the nth element encountered.

  • Java

  • Kotlin

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 Indexing

// evaluates to "Wireless communication"
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
		context, ieee, String.class);
val parser = SpelExpressionParser()
val context = SimpleEvaluationContext.forReadOnlyDataBinding().build()

// Inventions Array

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

// Members List

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

// List and Array Indexing

// evaluates to "Wireless communication"
val invention = parser.parseExpression("members[0].inventions[6]").getValue(
		context, ieee, String::class.java)

Indexing into Strings

字符串的第 n 个字符可以通过在方括号中指定索引来获取,如下面的示例所示。

The nth character of a string can be obtained by specifying the index within square brackets, as demonstrated in the following example.

字符串的 nth 字符将计算为 java.lang.String,而不是 java.lang.Character

The nth character of a string will evaluate to a java.lang.String, not a java.lang.Character.

  • Java

  • Kotlin

// evaluates to "T" (8th letter of "Nikola Tesla")
String character = parser.parseExpression("members[0].name[7]")
		.getValue(societyContext, String.class);
// evaluates to "T" (8th letter of "Nikola Tesla")
val character = parser.parseExpression("members[0].name[7]")
		.getValue(societyContext, String::class.java)

Indexing into Maps

映射的内容是通过在方括号中指定键值来获取的。 在以下示例中,由于 officers 映射的键是字符串,因此我们可以指定诸如 “president'” 的字符串字面量:

The contents of maps are obtained by specifying the key value within square brackets. In the following example, because keys for the officers map are strings, we can specify string literals such as ’president'`:

  • Java

  • Kotlin

// Officer's Map

// evaluates to Inventor("Pupin")
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);

String countryExpression = "officers['advisors'][0].placeOfBirth.country";

// setting values
parser.parseExpression(countryExpression)
		.setValue(societyContext, "Croatia");

// evaluates to "Croatia"
String country = parser.parseExpression(countryExpression)
		.getValue(societyContext, String.class);
// Officer's Map

// evaluates to Inventor("Pupin")
val pupin = parser.parseExpression("officers['president']")
		.getValue(societyContext, Inventor::class.java)

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

val countryExpression = "officers['advisors'][0].placeOfBirth.country"

// setting values
parser.parseExpression(countryExpression)
		.setValue(societyContext, "Croatia")

// evaluates to "Croatia"
val country = parser.parseExpression(countryExpression)
		.getValue(societyContext, String::class.java)

Indexing into Objects

可以通过在方括号中指定属性的名称来获取对象的属性。 这类似于基于其键访问映射的值。 以下示例演示了如何索引到对象以检索特定属性。

A property of an object can be obtained by specifying the name of the property within square brackets. This is analogous to accessing the value of a map based on its key. The following example demonstrates how to index into an object to retrieve a specific property.

  • Java

  • Kotlin

// Create an inventor to use as the root context object.
Inventor tesla = new Inventor("Nikola Tesla");

// evaluates to "Nikola Tesla"
String name = parser.parseExpression("#root['name']")
		.getValue(context, tesla, String.class);
// Create an inventor to use as the root context object.
val tesla = Inventor("Nikola Tesla")

// evaluates to "Nikola Tesla"
val name = parser.parseExpression("#root['name']")
		.getValue(context, tesla, String::class.java)

Indexing into Custom Structures

从 Spring Framework 6.2 开始,Spring 表达式语言支持通过允许开发者实现并注册带有 IndexAccessorEvaluationContext 的自定义结构进行索引。如果你想要支持依赖自定义索引访问器的 compilation 表达式,该索引访问器必须实现 CompilableIndexAccessor SPI。

Since Spring Framework 6.2, the Spring Expression Language supports indexing into custom structures by allowing developers to implement and register an IndexAccessor with the EvaluationContext. If you would like to support compilation of expressions that rely on a custom index accessor, that index accessor must implement the CompilableIndexAccessor SPI.

为了支持常见用例,Spring 提供了一个内置的 ReflectiveIndexAccessor,它是一个灵活的 IndexAccessor,它使用反射从目标对象的索引结构中读取并选择性地写入。 索引结构可以通过 public 读方法(在读取时)或 public 写方法(在写入时)进行访问。 读方法和写方法之间的关系基于一个惯例,该惯例适用于索引结构的典型实现。

To support common use cases, Spring provides a built-in ReflectiveIndexAccessor which is a flexible IndexAccessor that uses reflection to read from and optionally write to an indexed structure of a target object. The indexed structure can be accessed through a public read-method (when being read) or a public write-method (when being written). The relationship between the read-method and write-method is based on a convention that is applicable for typical implementations of indexed structures.

ReflectiveIndexAccessor 也实现了 CompilableIndexAccessor 以便支持 compilation 的字节码进行读写访问。但是,请注意,必须能够通过 public 类或 public 接口调用已配置的读取方法,以使其编译成功。

ReflectiveIndexAccessor also implements CompilableIndexAccessor in order to support compilation to bytecode for read access. Note, however, that the configured read-method must be invokable via a public class or public interface for compilation to succeed.

以下代码清单定义了一个 Color 枚举和一个 FruitMap 类型,它的行为类似于映射但并不实现 java.util.Map 接口。 因此,如果要在 SpEL 表达式中的 FruitMap 中进行索引,则需要注册一个 IndexAccessor。

The following code listings define a Color enum and FruitMap type that behaves like a map but does not implement the java.util.Map interface. Thus, if you want to index into a FruitMap within a SpEL expression, you will need to register an IndexAccessor.

public enum Color {
	RED, ORANGE, YELLOW
}
public class FruitMap {

	private final Map<Color, String> map = new HashMap<>();

	public FruitMap() {
		this.map.put(Color.RED, "cherry");
		this.map.put(Color.ORANGE, "orange");
		this.map.put(Color.YELLOW, "banana");
	}

	public String getFruit(Color color) {
		return this.map.get(color);
	}

	public void setFruit(Color color, String fruit) {
		this.map.put(color, fruit);
	}
}

可以 new ReflectiveIndexAccessor(FruitMap.class, Color.class, "getFruit") 创建 FruitMap 的只读 IndexAccessor。通过注册该访问器并注册 FruitMap 作为名为 #fruitMap 的变量,SpEL 表达式 #fruitMap[T(example.Color).RED] 将评估为 "cherry"

A read-only IndexAccessor for FruitMap can be created via new ReflectiveIndexAccessor(FruitMap.class, Color.class, "getFruit"). With that accessor registered and a FruitMap registered as a variable named #fruitMap, the SpEL expression #fruitMap[T(example.Color).RED] will evaluate to "cherry".

可以 new ReflectiveIndexAccessor(FruitMap.class, Color.class, "getFruit", "setFruit") 创建 FruitMap 的可读写 IndexAccessor。通过注册该访问器并注册 FruitMap 作为名为 #fruitMap 的变量,SpEL 表达式 #fruitMap[T(example.Color).RED] = 'strawberry' 可用于将红色颜色的水果映射从 "cherry" 更改为 "strawberry"

A read-write IndexAccessor for FruitMap can be created via new ReflectiveIndexAccessor(FruitMap.class, Color.class, "getFruit", "setFruit"). With that accessor registered and a FruitMap registered as a variable named #fruitMap, the SpEL expression #fruitMap[T(example.Color).RED] = 'strawberry' can be used to change the fruit mapping for the color red from "cherry" to "strawberry".

以下示例演示了如何注册一个 ReflectiveIndexAccessor 以在 FruitMap 中建立索引,然后在 SpEL 表达式中对 FruitMap 建立索引。

The following example demonstrates how to register a ReflectiveIndexAccessor to index into a FruitMap and then index into the FruitMap within a SpEL expression.

  • Java

  • Kotlin

// Create a ReflectiveIndexAccessor for FruitMap
IndexAccessor fruitMapAccessor = new ReflectiveIndexAccessor(
		FruitMap.class, Color.class, "getFruit", "setFruit");

// Register the IndexAccessor for FruitMap
context.addIndexAccessor(fruitMapAccessor);

// Register the fruitMap variable
context.setVariable("fruitMap", new FruitMap());

// evaluates to "cherry"
String fruit = parser.parseExpression("#fruitMap[T(example.Color).RED]")
		.getValue(context, String.class);
// Create a ReflectiveIndexAccessor for FruitMap
val fruitMapAccessor = ReflectiveIndexAccessor(
		FruitMap::class.java, Color::class.java, "getFruit", "setFruit")

// Register the IndexAccessor for FruitMap
context.addIndexAccessor(fruitMapAccessor)

// Register the fruitMap variable
context.setVariable("fruitMap", FruitMap())

// evaluates to "cherry"
val fruit = parser.parseExpression("#fruitMap[T(example.Color).RED]")
	.getValue(context, String::class.java)