属性、数组、列表、Map 和索引器

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

数字索引值是从零开始的,例如在访问 Java 数组的第 n 个元素时。
有关如何使用空安全运算符导航对象图和索引各种结构的详细信息, 请参阅 安全导航运算符 部分。

属性导航

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

// 评估结果为 1856
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);

// 评估结果为 "Smiljan"
String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);

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

索引数组和集合

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

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

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

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// 发明数组

// 评估结果为 "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
    context, tesla, String.class);

// 成员列表

// 评估结果为 "Nikola Tesla"
String name = parser.parseExpression("members[0].name").getValue(
    context, ieee, String.class);

// 列表和数组索引

// 评估结果为 "Wireless communication"
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
    context, ieee, String.class);

索引字符串

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

字符串的第 n 个字符将评估为 java.lang.String,而不是 java.lang.Character
// 评估结果为 "T" ("Nikola Tesla" 的第 8 个字母)
String character = parser.parseExpression("members[0].name[7]")
    .getValue(societyContext, String.class);

索引 Map

Map 的内容通过在方括号内指定键值来获取。 在下例中,因为 officers Map 的键是字符串,我们可以指定字符串字面量,如 'president'

// 官员 Map

// 评估结果为 Inventor("Pupin")
Inventor pupin = parser.parseExpression("officers['president']")
    .getValue(societyContext, Inventor.class);

// 评估结果为 "Idvor"
String city = parser.parseExpression("officers['president'].placeOfBirth.city")
    .getValue(societyContext, String.class);

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

// 设置值
parser.parseExpression(countryExpression)
    .setValue(societyContext, "Croatia");

// 评估结果为 "Croatia"
String country = parser.parseExpression(countryExpression)
    .getValue(societyContext, String.class);

索引对象

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

// 创建一个发明家作为根上下文对象。
Inventor tesla = new Inventor("Nikola Tesla");

// 评估结果为 "Nikola Tesla"
String name = parser.parseExpression("#root['name']")
    .getValue(context, tesla, String.class);

索引自定义结构

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

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

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

以下代码清单定义了一个 Color 枚举和一个 FruitMap 类型,该类型表现得像 map 但不实现 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);
  }
}
// 为 FruitMap 创建一个 ReflectiveIndexAccessor
IndexAccessor fruitMapAccessor = new ReflectiveIndexAccessor(
    FruitMap.class, Color.class, "getFruit", "setFruit");

// 注册 FruitMap 的 IndexAccessor
context.addIndexAccessor(fruitMapAccessor);

// 注册 fruitMap 变量
context.setVariable("fruitMap", new FruitMap());

// 评估结果为 "cherry"
String fruit = parser.parseExpression("#fruitMap[T(example.Color).RED]")
    .getValue(context, String.class);