Properties, Arrays, Lists, Maps, and Indexers

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

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

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

Property Navigation

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

  • 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

Indexing into Arrays and Collections

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

如果索引集合是 java.util.List,则可以通过 list.get(n) 直接访问第 n 个元素。 对于任何其他类型的 Collection,将通过使用其 Iterator 遍历集合并返回遇到的第 n 个元素来访问第 n 个元素。

  • 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 个字符可以通过在方括号中指定索引来获取,如下面的示例所示。

字符串的 nth 字符将计算为 java.lang.String,而不是 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'” 的字符串字面量:

  • 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

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

  • 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。

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

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

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

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

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

  • 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)