属性、数组、列表、映射(Maps)和索引器(Indexers)
Spring表达式语言(Spring Expression Language,简称SpEL)提供了遍历对象图以及访问各种结构中元素的能力。
数值索引值是从零开始的,例如在Java中访问数组的第n个元素时。
有关如何使用空值安全运算符在对象图中导航以及访问各种结构中的内容的详细信息,请参阅安全导航运算符部分。
属性导航
你可以通过使用点(.)来表示嵌套属性值,从而在对象图中导航属性引用。Inventor类的实例pupin和tesla被填充了在示例中使用的类部分列出的数据。要向下导航对象图以获取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 + 1900 和 PlaceOfBirth.City。此外,还可以通过方法调用来访问属性,例如使用 getPlaceOfBirth().getCity() 而不是 placeOfBirth.city。
在数组和集合中索引
数组或集合(例如 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)
在字符串中索引
可以通过在方括号内指定索引来获取字符串中的第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)
在映射中索引
地图(map)的内容是通过在方括号内指定键值对来获得的。在下面的例子中,因为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)
对对象进行索引
可以通过在方括号内指定属性的名称来获取对象的某个属性。这类似于根据键来访问映射(map)中的值。以下示例演示了如何通过索引来检索对象中的特定属性。
- 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 Expression Language 支持通过允许开发者实现并注册一个 IndexAccessor 到 EvaluationContext 中来访问自定义结构。如果你想支持依赖于自定义索引访问器的表达式的 编译,那么该索引访问器必须实现 CompilableIndexAccessor SPI。
为了支持常见的使用场景,Spring 提供了一个内置的 ReflectiveIndexAccessor,这是一个灵活的 IndexAccessor,它利用反射来读取目标对象的索引结构,并可选择性地对其进行写入操作。在读取时,可以通过公共的读取方法(public read-method)来访问索引结构;在写入时,则可以通过公共的写入方法(public write-method)来访问该结构。读取方法和写入方法之间的关系基于一种约定,这种约定适用于典型的索引结构实现。
ReflectiveIndexAccessor 还实现了 CompilableIndexAccessor,以便支持将读取访问操作编译为字节码(参见 compilation)。不过请注意,要使编译成功,所配置的读取方法必须能够通过 public 类或 public 接口被调用。
以下代码片段定义了一个Color枚举和一个FruitMap类型,后者表现得像一个映射(map),但并没有实现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") 创建一个只读的 IndexAccessor 用于 FruitMap。注册了这个访问器,并将 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)