属性、数组、列表、映射和索引器

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

数值索引值是零基的,例如在 Java 中访问数组的第 n 个元素时。

有关如何使用空安全运算符导航对象图和索引到各种结构的详细信息,请参阅 安全导航运算符 部分。

属性导航

您可以通过使用句点来指示嵌套属性值来导航对象图中的属性引用。Inventor 类的实例 pupintesla 已使用 示例中使用的类 部分中列出的数据填充。 要导航_下_对象图并获取 Tesla 的出生年份和 Pupin 的出生城市,我们使用以下表达式:

  • 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

索引数组和集合

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

如果索引集合是 java.util.List,则第 n 个元素将直接通过 list.get(n) 访问。 对于任何其他类型的 Collection,第 n 个元素将通过使用其 Iterator 迭代集合并返回遇到的第 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)

索引字符串

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

字符串的第 n 个字符将评估为 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)

索引映射

映射的内容通过在方括号内指定键值来获取。在以下示例中,由于 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)

索引对象

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

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

索引自定义结构

自 Spring Framework 6.2 起,Spring 表达式语言通过允许开发人员实现 IndexAccessor 并将其注册到 EvaluationContext 来支持索引自定义结构。如果您希望支持 依赖自定义索引访问器的表达式的 编译, 则该索引访问器必须实现 CompilableIndexAccessor SPI。

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

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

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

package example;

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)