Spring表达式语言(SpEL)

https://docs.spring.io/spring-framework/reference/core/expressions.html

Spring表达式语言(简称“SpEL”)是一种强大的表达式语言,支持在运行时查询和操作对象图。该语言的语法类似于Jakarta表达式语言,但提供了额外的功能,最显著的是方法调用和基本字符串模板功能。

虽然还有其它几种Java表达式语言可用,例如OGNL、MVEL和JBoss EL等,但Spring表达式语言是为了向Spring社区提供一个受良好支持的单一表达式语言而创建的,该语言可用于Spring产品组合中的所有产品。其语言功能是由Spring产品组合中的项目需求驱动的,包括Spring Tools for Eclipse中的代码完成支持的工具要求。尽管如此,SpEL是基于一个技术中立的应用程序接口(API)的,如果需要,它可以让其它表达式语言实现集成。

虽然SpEL是Spring产品组合中表达式评估的基础,但它并不直接与Spring相关,可以独立使用。为了自成一体,本章中的许多示例都将SpEL视为独立的表达式语言。这需要创建一些引导基础设施类,例如解析器。大多数Spring用户无需处理此基础设施,而只需编写用于评估的表达式字符串。这种典型用法的一个示例是将SpEL集成到基于XML或注解的bean定义中。

表达式语言支持以下功能:

  • 字面量表达式
  • 访问属性、数组、列表和映射
  • 内联列表
  • 内联映射
  • 数组构造
  • 关系运算符
  • 正则表达式
  • 逻辑运算符
  • 字符串运算符
  • 数学运算符
  • 赋值
  • 类型表达式
  • 方法调用
  • 构造函数调用
  • 变量
  • 用户定义的函数
  • Bean引用
  • 三元、Elvis和安全导航运算符
  • 集合投影
  • 集合选择
  • 模板表达式

评估(Evaluation)

以下代码演示了如何使用SpEL API评估字面字符串表达式“Hello World”。

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String message = (String) exp.getValue();

消息变量的值为“Hello World”。

你最可能使用的SpEL类和接口位于org.springframework.expression包及其子包中,如spel.support

ExpressionParser接口负责解析表达式字符串。在前面的示例中,表达式字符串是由单引号包围的字符串字面量。Expression接口负责评估定义的表达式字符串。调用parser.parseExpression(…​)exp.getValue(…​)时可能会抛出两种类型的异常,分别是ParseExceptionEvaluationException

SpEL支持多种功能,如调用方法、访问属性和调用构造函数。

在以下的方法调用示例中,我们在字符串字面量“Hello World”上调用concat方法。

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
String message = (String) exp.getValue();

现在message的值为“Hello World!”。

以下示例演示了如何访问字符串字面量“Hello World”的Bytes JavaBean属性。

ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes()'
Expression exp = parser.parseExpression("'Hello World'.bytes");
byte[] bytes = (byte[]) exp.getValue();

SpEL还支持使用标准点符号(如prop1.prop2.prop3)来访问嵌套属性,并支持相应的属性值设置。还可以访问公共字段。

以下示例展示了如何使用点符号来获取字符串字面量的长度。

ExpressionParser parser = new SpelExpressionParser();

// invokes 'getBytes().length'
Expression exp = parser.parseExpression("'Hello World'.bytes.length");
int length = (Integer) exp.getValue();

可以调用String的构造函数来替代使用字符串字面量,如下例所示。

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("new String('hello world').toUpperCase()");
String message = exp.getValue(String.class);

请注意泛型方法的使用:public <T> T getValue(Class<T> desiredResultType)。使用此方法可以消除将表达式值转换为所需结果类型的需要。如果无法将值转换为类型T,或者无法通过已注册的类型转换器进行转换,则会抛出EvaluationException

SpEL的更常见用法是提供一个表达式字符串,该字符串与特定的对象实例(称为根对象)进行评估。以下示例演示了如何从Inventor类的实例中检索name属性,以及如何在布尔表达式中引用name属性。

// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();

Expression exp = parser.parseExpression("name"); // Parse name as an expression
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"

exp = parser.parseExpression("name == 'Nikola Tesla'");
boolean result = exp.getValue(tesla, Boolean.class);
// result == true

理解EvaluationContext

在评估表达式以解决属性、方法或字段并帮助进行类型转换时,使用EvaluationContext接口。Spring提供了两种实现。

  • SimpleEvaluationContext:暴露SpEL语言功能和配置选项的子集,用于不需要完整的SpEL语言语法并且可以合理限制的表达式类别。示例包括但不限于数据绑定表达式和基于属性的过滤器。
  • StandardEvaluationContext:暴露完整的SpEL语言功能和配置选项集。可以使用它来指定默认的根对象,并配置所有可用的评估相关策略。

SimpleEvaluationContext旨在仅支持SpEL语言语法的子集。它不包括Java类型引用、构造函数和bean引用。它还要求你明确选择在表达式中对属性和方法的支持级别。默认情况下,create()静态工厂方法仅启用对属性的读取访问。还可以获得一个构建器来配置所需的确切支持级别,针对以下一个或多个组合。

  • 仅自定义PropertyAccessor(无反射)
  • 仅用于只读访问的数据绑定属性
  • 用于读写访问的数据绑定属性

类型转换

默认情况下,SpEL使用Spring核心(org.springframework.core.convert.ConversionService)中提供的转换服务。此转换服务提供了许多常见的内置转换器,但也可以完全扩展,以便可以在类型之间添加自定义转换。此外,它还支持泛型。这意味着当在表达式中使用泛型类型时,SpEL会尝试进行转换,以保持所遇到对象的类型正确性。

这在实践中意味着什么?假设使用setValue()方法分配正在设置的List属性。该属性的实际类型是List<Boolean>。SpEL识别出列表中的元素需要在放入列表之前转换为Boolean。以下示例显示了如何执行此操作。

class Simple {
	public List<Boolean> booleanList = new ArrayList<>();
}

Simple simple = new Simple();
simple.booleanList.add(true);

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");

// b is false
Boolean b = simple.booleanList.get(0);

Parser配置

可以通过使用解析器配置对象(org.springframework.expression.spel.SpelParserConfiguration)来配置SpEL表达式解析器。配置对象控制一些表达式组件的行为。例如,如果对数组或集合进行索引,并且指定索引处的元素为null,则SpEL可以自动创建该元素。当使用由一系列属性引用组成的表达式时,这很有用。如果对数组或列表进行索引并指定超出数组或列表当前大小的索引,则SpEL可以自动增长数组或列表以适应该索引。为了在指定的索引处添加元素,SpEL将在设置指定值之前尝试使用元素类型的默认构造函数创建元素。如果元素类型没有默认构造函数,则将null添加到数组或列表中。如果没有内置或自定义转换器知道如何设置值,则指定索引处的数组或列表中仍将保留null。以下示例演示了如何自动增长列表。

class Demo {
	public List<String> list;
}

// Turn on:
// - auto null reference initialization
// - auto collection growing
SpelParserConfiguration config = new SpelParserConfiguration(true, true);

ExpressionParser parser = new SpelExpressionParser(config);

Expression expression = parser.parseExpression("list[3]");

Demo demo = new Demo();

Object o = expression.getValue(demo);

// demo.list will now be a real collection of 4 entries
// Each entry is a new empty String

默认情况下,SpEL表达式不能包含超过10,000个字符;但是,maxExpressionLength是可配置的。如果以编程方式创建SpelExpressionParser,则可以在创建提供给SpelExpressionParserSpelParserConfiguration时指定自定义的maxExpressionLength。如果希望设置用于在ApplicationContext中解析SpEL表达式的maxExpressionLength,例如在XML bean定义、@Value等中,可以将JVM系统属性或名为spring.context.expression.maxLength的Spring属性设置为你的应用程序所需的最大表达式长度。

SpEL 编译

Spring为SpEL表达式提供了一个基本的编译器。表达式通常是解释性的,这在评估过程中提供了大量的动态灵活性,但并未提供最佳性能。对于偶尔使用的表达式来说,这没问题,但是,当被其它组件(如Spring Integration)使用时,性能可能非常重要,而且实际上并不需要这种动态性。

SpEL编译器旨在解决这一需求。在评估过程中,编译器生成一个Java类,该类在运行时体现了表达式的行为,并使用该类实现更快的表达式评估。由于表达式周围缺乏类型,编译器在执行编译时会使用在解释性评估表达式期间收集的信息。例如,它无法仅从表达式中知道属性引用的类型,但在第一次解释性评估期间,它会发现它是什么。当然,如果各种表达式元素的类型随时间变化,那么基于此类派生信息进行编译可能会在以后引起麻烦。因此,编译最适合于在重复评估中类型信息不会更改的表达式。

考虑以下基本表达式。

someArray[0].someProperty.someOtherProperty < 0.1

由于前面的表达式涉及数组访问、一些属性取消引用和数值运算,因此性能提升可能非常明显。在一个50,000次迭代的微型基准测试示例中,使用解释器进行评估需要75毫秒,而使用表达式的编译版本只需要3毫秒。

编译器配置

编译器默认不会开启,但可以通过两种不同的方式打开它。可以通过使用解析器配置过程(或使用嵌入到其它组件中的SpEL用法时的Spring属性来打开它。本节将讨论这两种选项。

编译器可以在三种模式之一中运行,这些模式在org.springframework.expression.spel.SpelCompilerMode枚举中定义。这些模式如下。

  • OFF (默认):编译器关闭。
  • IMMEDIATE:在立即模式下,表达式会尽快被编译。这通常是在第一次解释性评估之后。如果编译的表达式失败(通常是由于类型更改,如前面所述),则表达式评估的调用者会收到一个异常。
  • MIXED:在混合模式下,表达式会在解释模式和编译模式之间随时间静默切换。经过一定次数的解释运行后,它们切换到编译形式,如果编译形式出现问题(如前所述的类型更改),则表达式将自动切换回解释形式。一段时间后,它可能会生成另一个编译形式并切换到该形式。基本上,用户在IMMEDIATE模式下得到的异常会在内部进行处理。

存在IMMEDIATE模式是因为MIXED模式可能会对具有副作用的表达式造成问题。如果编译后的表达式在部分成功之后崩溃,那么它可能已经做了一些影响系统状态的事情。如果发生这种情况,调用者可能不希望它在解释模式下静默地重新运行,因为表达式的一部分可能会被运行两次。

选择模式后,使用SpelParserConfiguration来配置解析器。以下示例演示了如何执行此操作。

SpelParserConfiguration config = new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,
		this.getClass().getClassLoader());

SpelExpressionParser parser = new SpelExpressionParser(config);

Expression expr = parser.parseExpression("payload");

MyMessage message = new MyMessage();

Object payload = expr.getValue(message);

当你指定编译器模式时,还可以指定一个ClassLoader (传递null是允许的)。编译的表达式在提供的任何类加载器下创建的子ClassLoader 中定义。重要的是要确保,如果指定了ClassLoader ,它能够看到表达式求值过程中涉及的所有类型。如果你没有指定ClassLoader ,将使用默认ClassLoader (通常是表达式求值期间运行的线程的上下文ClassLoader )。

配置编译器的第二种方法是在SpEL嵌入到其它组件中使用,并且可能无法通过配置对象对其进行配置。在这种情况下,可以通过JVM系统属性(或通过SpringProperties机制)将spring.expression.compiler.mode属性设置为SpelCompilerMode枚举值之一(offimmediatemixed)。

编译器限制

Spring不支持编译所有类型的表达式。主要的重点是可能在性能关键上下文中使用的常见表达式。以下类型的表达式无法编译。

  • 涉及赋值的表达式
  • 依赖于转换服务的表达式
  • 使用自定义解析器或访问器的表达式
  • 使用重载运算符的表达式
  • 使用选择或投影的表达式

将来可能会支持其它类型表达式的编译。

Bean定义中的表达式

可以使用SpEL表达式与基于XML或基于注解的配置元数据来定义BeanDefinition实例。在这两种情况下,定义表达式的语法形式为#{ <expression string> }

XML 配置

可以使用表达式设置属性或构造函数参数的值,如下所示:

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
	<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

	<!-- other properties -->
</bean>

应用程序上下文中的所有bean都可作为具有其通用bean名称的预定义变量使用。这包括标准上下文bean,如environment (类型为org.springframework.core.env.Environment),以及systemPropertiessystemEnvironment(类型为Map<String, Object>),用于访问运行时环境。

以下示例显示了如何将systemProperties bean作为SpEL变量进行访问:

<bean id="taxCalculator" class="org.spring.samples.TaxCalculator">
	<property name="defaultLocale" value="#{ systemProperties['user.region'] }"/>

	<!-- other properties -->
</bean>

不必在此处使用#符号作为预定义变量的前缀。

还可以通过名称引用其它bean属性,如下所示:

<bean id="numberGuess" class="org.spring.samples.NumberGuess">
	<property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>

	<!-- other properties -->
</bean>

<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
	<property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>

	<!-- other properties -->
</bean>

注解配置

要指定默认值,可以将@Value注解放在字段、方法或方法或构造函数的参数上。

以下示例设置了字段的默认值:

public class FieldValueTestBean {

	@Value("#{ systemProperties['user.region'] }")
	private String defaultLocale;

	public void setDefaultLocale(String defaultLocale) {
		this.defaultLocale = defaultLocale;
	}

	public String getDefaultLocale() {
		return this.defaultLocale;
	}
}

以下示例显示了在属性setter 方法上的等效操作:

public class PropertyValueTestBean {

	private String defaultLocale;

	@Value("#{ systemProperties['user.region'] }")
	public void setDefaultLocale(String defaultLocale) {
		this.defaultLocale = defaultLocale;
	}

	public String getDefaultLocale() {
		return this.defaultLocale;
	}
}

Autowired方法和构造函数也可以使用@Value注解,如下所示:

public class SimpleMovieLister {

	private MovieFinder movieFinder;
	private String defaultLocale;

	@Autowired
	public void configure(MovieFinder movieFinder,
			@Value("#{ systemProperties['user.region'] }") String defaultLocale) {
		this.movieFinder = movieFinder;
		this.defaultLocale = defaultLocale;
	}

	// ...
}
public class MovieRecommender {

	private String defaultLocale;

	private CustomerPreferenceDao customerPreferenceDao;

	public MovieRecommender(CustomerPreferenceDao customerPreferenceDao,
			@Value("#{systemProperties['user.country']}") String defaultLocale) {
		this.customerPreferenceDao = customerPreferenceDao;
		this.defaultLocale = defaultLocale;
	}

	// ...
}

语言参考

字面量表达式

SpEL支持以下类型的字面量表达式。
1)String
字符串可以由单引号(')或双引号(")定界。要在由单引号定界的字符串文字中包含单引号,请使用两个相邻的单引号字符。类似地,要在由双引号定界的字符串文字中包含双引号,请使用两个相邻的双引号字符。

2)Number
数字支持使用负号、指数表示法和小数点。

  • 整数: intlong
  • 十六进制:intlong
  • 实数:floatdouble
    默认情况下,使用Double.parseDouble()解析实数。

3)Boolean:truefalse

4)Null:null

由于Spring表达式语言的设计和实现,字面数字始终以正数的形式在内部存储。

例如,-2在内部以正数2的形式存储,然后在评估表达式时(通过计算0-2的值)取反。

这意味着在Java中,不可能表示等于该类型数字最小值的负字面数字。例如,Java中int的最小支持值为Integer.MIN_VALUE,其值为-2147483648。但是,如果你在SpEL表达式中包含-2147483648,将抛出异常,通知无法将值2147483648解析为int(因为它超过了Integer.MAX_VALUE的值,即2147483647)。

如果你需要在SpEL表达式中使用特定类型的数字的最小值,则可以引用相应包装类型的MIN_VALUE常量(例如Integer.MIN_VALUELong.MIN_VALUE等)或计算最小值。例如,要使用最小的整数值:

  • T(Integer).MIN_VALUE - 需要StandardEvaluationContext
  • -2^31 - 可以使用任何类型的EvaluationContext

以下列表显示了字面量的简单用法。通常,它们不会像这样单独使用,而是作为更复杂表达式的一部分 - 例如,在逻辑比较运算符的一侧使用字面量或作为方法的参数。

ExpressionParser parser = new SpelExpressionParser();

// evaluates to "Hello World"
String helloWorld = (String) parser.parseExpression("'Hello World'").getValue();

// evaluates to "Tony's Pizza"
String pizzaParlor = (String) parser.parseExpression("'Tony''s Pizza'").getValue();

double avogadrosNumber = (Double) parser.parseExpression("6.0221415E+23").getValue();

// evaluates to 2147483647
int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();

boolean trueValue = (Boolean) parser.parseExpression("true").getValue();

Object nullValue = parser.parseExpression("null").getValue();

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

使用属性引用来导航很简单。为此,请使用点号表示嵌套属性值。在示例部分中使用的类中列出的数据填充了Inventor类的实例pupintesla。为了“向下”导航对象图并获取Tesla的出生年份和Pupin的出生城市,我们使用以下表达式:

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

String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);

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

数组和列表的内容通过使用方括号表示法获取,如下所示:

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 navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
		context, ieee, String.class);

通过指定方括号内的文字键值,可以获取映射的内容。在以下示例中,因为officers映射的键是字符串,所以我们可以指定字符串文字:

// Officer's Dictionary

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

// setting values
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
		societyContext, "Croatia");

内联列表(Inline Lists)

可以使用{}表示法直接在表达式中表示列表。

// evaluates to a Java list containing the four numbers
List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context);

List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context);

单独的{}表示一个空列表。出于性能原因,如果列表本身完全由固定的文字量组成,则创建一个常量列表来表示该表达式(而不是在每次评估时构建新列表)。

内联映射

还可以使用`{key:value}`表示法直接在表达式中表示映射。以下示例显示了如何执行此操作:
// evaluates to a Java map containing the two entries
Map inventorInfo = (Map) parser.parseExpression("{name:'Nikola',dob:'10-July-1856'}").getValue(context);

Map mapOfMaps = (Map) parser.parseExpression("{name:{first:'Nikola',last:'Tesla'},dob:{day:10,month:'July',year:1856}}").getValue(context);

单独的{:}表示一个空映射。出于性能原因,如果映射本身由固定的字面量或其它嵌套常量结构(列表或映射)组成,则创建一个常量映射来表示该表达式(而不是在每次评估时构建新映射)。映射键的引用是可选的(除非键包含句点(.))。上面的示例不使用带引号的键。

数组构造

可以使用熟悉的Java语法构建数组,并可选地提供一个初始化程序以在构建时填充数组。以下示例显示了如何执行此操作:

int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context);

// Array with initializer
int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context);

// Multi dimensional array
int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context);

当前,在构造多维数组时,你无法提供初始化程序。

方法

可以使用典型的Java编程语法调用方法。还可以在字面量上调用方法。还支持可变参数。以下示例显示了如何调用方法:

// string literal, evaluates to "bc"
String bc = parser.parseExpression("'abc'.substring(1, 3)").getValue(String.class);

// evaluates to true
boolean isMember = parser.parseExpression("isMember('Mihajlo Pupin')").getValue(
		societyContext, Boolean.class);

操作符

Spring表达式语言支持以下类型的运算符:

  • 关系运算符
  • 逻辑运算符
  • 字符串运算符
  • 数学运算符
  • 赋值运算符
  • 重载运算符

关系运算符

关系运算符(等于、不等于、小于、小于或等于、大于和大于或等于)通过使用标准运算符表示法得到支持。这些运算符适用于 Number 类型以及实现 Comparable 接口的类型。以下列表显示了一些关系运算符的示例:

// evaluates to true
boolean trueValue = parser.parseExpression("2 == 2").getValue(Boolean.class);

// evaluates to false
boolean falseValue = parser.parseExpression("2 < -5.0").getValue(Boolean.class);

// evaluates to true
boolean trueValue = parser.parseExpression("'black' < 'block'").getValue(Boolean.class);

// uses CustomValue:::compareTo
boolean trueValue = parser.parseExpression("new CustomValue(1) < new CustomValue(2)").getValue(Boolean.class);

null 进行大于和小于比较时遵循一个简单的规则:null 被视为空(即不是零)。因此,任何其它值总是大于 nullX > null 总是为true),没有任何其它值小于空(X < null 总是为false)。

如果你更喜欢数值比较,请避免基于数字的 null 比较,而改为与零进行比较(例如,X > 0X < 0)。

每个符号运算符也可以指定为纯文本等价物。这避免了所使用的符号对嵌入表达式的文档类型具有特殊意义的问题(例如在 XML 文档中)。文本等价物如下:

  • lt (<)
  • gt (>)
  • le (<=)
  • ge (>=)
  • eq (==)
  • ne (!=)

所有的文本运算符都是不区分大小写的。

除了标准的关系运算符之外,SpEL 还支持 betweeninstanceof 和基于正则表达式的matches 运算符。以下列表显示了这三个运算符的示例:

boolean result;

// evaluates to true
result = parser.parseExpression(
		"1 between {1, 5}").getValue(Boolean.class);

// evaluates to false
result = parser.parseExpression(
		"1 between {10, 15}").getValue(Boolean.class);

// evaluates to true
result = parser.parseExpression(
		"'elephant' between {'aardvark', 'zebra'}").getValue(Boolean.class);

// evaluates to false
result = parser.parseExpression(
		"'elephant' between {'aardvark', 'cobra'}").getValue(Boolean.class);

// evaluates to true
result = parser.parseExpression(
		"123 instanceof T(Integer)").getValue(Boolean.class);

// evaluates to false
result = parser.parseExpression(
		"'xyz' instanceof T(Integer)").getValue(Boolean.class);

// evaluates to true
result = parser.parseExpression(
		"'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

// evaluates to false
result = parser.parseExpression(
		"'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

between 运算符的语法是 <input> between {<range_begin>, <range_end>},这实际上是 <input> >= <range_begin> && <input> <= <range_end>} 的快捷方式。

因此,1 between {1, 5} 的结果为 true,而 1 between {5, 1} 的结果为 false

注意,原始类型会立即被装箱为其包装类型。例如,1 instanceof T(int) 的结果为 false,而 1 instanceof T(Integer) 的结果为 true

逻辑运算符

SpEL 支持以下逻辑(布尔)运算符:

  • and (&&)
  • or (||)
  • not (!)

所有的文本运算符都是不区分大小写的。

以下示例显示了如何使用逻辑运算符:

// -- AND --

// evaluates to false
boolean falseValue = parser.parseExpression("true and false").getValue(Boolean.class);

// evaluates to true
String expression = "isMember('Nikola Tesla') and isMember('Mihajlo Pupin')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- OR --

// evaluates to true
boolean trueValue = parser.parseExpression("true or false").getValue(Boolean.class);

// evaluates to true
String expression = "isMember('Nikola Tesla') or isMember('Albert Einstein')";
boolean trueValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

// -- NOT --

// evaluates to false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);

// -- AND and NOT --

String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);

字符串操作符

可以在字符串上使用以下运算符。

  • 连接(+)
  • 减法(-):用于包含单个字符的字符串
  • 重复(*)

下示例展示了字符串运算符的使用:

// -- Concatenation --

// evaluates to "hello world"
String helloWorld = parser.parseExpression("'hello' + ' ' + 'world'")
		.getValue(String.class);

// -- Character Subtraction --

// evaluates to 'a'
char ch = parser.parseExpression("'d' - 3")
		.getValue(char.class);

// -- Repeat --

// evaluates to "abcabc"
String repeated = parser.parseExpression("'abc' * 2")
		.getValue(String.class);

数学运算符

可以在数字上使用以下运算符,并强制执行标准的运算符优先级。

  • 加法(+)
  • 减法(-)
  • 递增(++)
  • 递减(–)
  • 乘法(*)
  • 除法(/)
  • 模数(%)
  • 指数幂(^)

除法和模数运算符也可以指定为纯文本等价物。这避免了使用的符号对于嵌入表达式的文档类型具有特殊意义的问题(例如在 XML 文档中)。文本等价物是:

  • div (/)
  • mod (%)

所有的文本运算符都是不区分大小写的。

递增和递减运算符可以与可以写入的变量或属性一起使用前缀(++A--A)或后缀(A++A--)表示法。

以下示例展示了数学运算符的使用:

Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

// -- Addition --

int two = parser.parseExpression("1 + 1").getValue(int.class);  // 2

// -- Subtraction --

int four = parser.parseExpression("1 - -3").getValue(int.class);  // 4

double d = parser.parseExpression("1000.00 - 1e4").getValue(double.class);  // -9000

// -- Increment --

// The counter property in Inventor has an initial value of 0.

// evaluates to 2; counter is now 1
two = parser.parseExpression("counter++ + 2").getValue(context, inventor, int.class);

// evaluates to 5; counter is now 2
int five = parser.parseExpression("3 + ++counter").getValue(context, inventor, int.class);

// -- Decrement --

// The counter property in Inventor has a value of 2.

// evaluates to 6; counter is now 1
int six = parser.parseExpression("counter-- + 4").getValue(context, inventor, int.class);

// evaluates to 5; counter is now 0
five = parser.parseExpression("5 + --counter").getValue(context, inventor, int.class);

// -- Multiplication --

six = parser.parseExpression("-2 * -3").getValue(int.class);  // 6

double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(double.class);  // 24.0

// -- Division --

int minusTwo = parser.parseExpression("6 / -3").getValue(int.class);  // -2

double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(double.class);  // 1.0

// -- Modulus --

int three = parser.parseExpression("7 % 4").getValue(int.class);  // 3

int oneInt = parser.parseExpression("8 / 5 % 2").getValue(int.class);  // 1

// -- Exponential power --

int maxInt = parser.parseExpression("(2^31) - 1").getValue(int.class);  // Integer.MAX_VALUE

int minInt = parser.parseExpression("-2^31").getValue(int.class);  // Integer.MIN_VALUE

// -- Operator precedence --

int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(int.class);  // -21

值运算符

要设置属性,请使用赋值运算符(=)。这通常是在调用 setValue 的过程中完成的,但也可以在调用 getValue 的过程中完成。以下列表显示了使用赋值运算符的两种方法:

Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

parser.parseExpression("name").setValue(context, inventor, "Aleksandar Seovic");

// alternatively
String aleks = parser.parseExpression(
		"name = 'Aleksandar Seovic'").getValue(context, inventor, String.class);

重载运算符

默认情况下,SpEL 的 Operation 枚举(ADD、SUBTRACT、DIVIDE、MULTIPLY、MODULUS 和 POWER)中定义的数学运算支持简单的类型,如数字。通过提供 OperatorOverloader 的实现,表达式语言可以支持这些运算符用于其它类型。

例如,如果我们想要重载 ADD 运算符以允许使用 + 符号将两个列表连接在一起,我们可以实现一个自定义的 OperatorOverloader,如下所示。

pubic class ListConcatenation implements OperatorOverloader {

	@Override
	public boolean overridesOperation(Operation operation, Object left, Object right) {
		return (operation == Operation.ADD &&
				left instanceof List && right instanceof List);
	}

	@Override
	public Object operate(Operation operation, Object left, Object right) {
		if (operation == Operation.ADD &&
				left instanceof List list1 && right instanceof List list2) {

			List result = new ArrayList(list1);
			result.addAll(list2);
			return result;
		}
		throw new UnsupportedOperationException(
			"No overload for operation %s and operands [%s] and [%s]"
				.formatted(operation, left, right));
	}
}

如果我们在 StandardEvaluationContext 中将 ListConcatenation 注册为 OperatorOverloader,那么我们就可以像下面的示例那样评估像 {1, 2, 3} + {4, 5} 这样的表达式。

StandardEvaluationContext context = new StandardEvaluationContext();
context.setOperatorOverloader(new ListConcatenation());

// evaluates to a new list: [1, 2, 3, 4, 5]
parser.parseExpression("{1, 2, 3} + {2 + 2, 5}").getValue(context, List.class);

OperatorOverloader 不会更改运算符的默认语义。例如,在上面的示例中,2 + 2 仍然评估为 4

用重载运算符的任何表达式都无法编译。

类型

可以使用特殊的 T 运算符来指定 java.lang.Class(类型)的实例。使用此运算符还可以调用静态方法。StandardEvaluationContext 使用 TypeLocator 来查找类型,而 StandardTypeLocator(可以替换)则是基于对 java.lang 包的理解构建的。这意味着对 java.lang 包内类型的 T() 引用不需要完全限定,但所有其它类型引用都必须完全限定。以下示例展示了如何使用 T 运算符:

Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);

Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);

boolean trueValue = parser.parseExpression(
		"T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR")
		.getValue(Boolean.class);

如果你的应用程序或框架管理自己的 EvaluationContext,你可能需要手动配置一个带有特定 ClassLoaderStandardTypeLocator,以确保 SpEL 表达式解析器能够可靠地找到用户类型。

例如,spring-context 模块中的 StandardBeanExpressionResolver 使用相应 BeanFactory 的 bean ClassLoader 配置 StandardTypeLocator

构造函数

可以使用 new 运算符调用构造函数。对于位于 java.lang 包(IntegerFloatString 等)之外的所有类型,应该使用完全限定的类名。以下示例展示了如何使用 new 运算符调用构造函数:

Inventor einstein = p.parseExpression(
		"new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')")
		.getValue(Inventor.class);

// create new Inventor instance within the add() method of List
p.parseExpression(
		"Members.add(new org.spring.samples.spel.inventor.Inventor(
			'Albert Einstein', 'German'))").getValue(societyContext);

变量

可以通过使用 #variableName 语法在表达式中引用变量。变量是通过在 EvaluationContext 实现中使用 setVariable() 方法设置的。

变量名必须以字母、下划线或美元符号开头。

变量名必须由以下支持的类型字符中的一个或多个组成:

  • 字母:对于 java.lang.Character.isLetter(char) 返回 true 的任何字符。这包括诸如 A 到 Z、a 到 z、ü、ñ 和 é 等字母,以及其它字符集中的字母,如中文、日文、西里尔文等。
  • 数字:0 到 9
  • 下划线:_
  • 美元符号:$

由于变量在评估上下文中与函数共享一个公共的命名空间,因此必须小心确保变量名和函数名不重叠。

下示例展示了如何使用变量。

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");

EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("newName", "Mike Tesla");

parser.parseExpression("name = #newName").getValue(context, tesla);
System.out.println(tesla.getName());  // "Mike Tesla"

#this 和 #root变量

#this 变量始终定义,并引用当前评估对象(针对该对象解析非限定引用)。#root 变量始终定义,并引用根上下文对象。虽然 #this 可能随着表达式组件的评估而变化,但 #root 始终引用根。

以下示例展示了如何结合集合选择使用 #this 变量。

// Create a list of prime integers.
List<Integer> primes = List.of(2, 3, 5, 7, 11, 13, 17);

// Create parser and set variable 'primes' as the list of integers.
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
context.setVariable("primes", primes);

// Select all prime numbers > 10 from the list (using selection ?{...}).
String expression = "#primes.?[#this > 10]";

// Evaluates to a list containing [11, 13, 17].
List<Integer> primesGreaterThanTen =
		parser.parseExpression(expression).getValue(context, List.class);

以下示例展示了如何结合集合投影一起使用 #this#root 变量。

// Create parser and evaluation context.
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

// Create an inventor to use as the root context object.
Inventor tesla = new Inventor("Nikola Tesla");
tesla.setInventions("Telephone repeater", "Tesla coil transformer");

// Iterate over all inventions of the Inventor referenced as the #root
// object, and generate a list of strings whose contents take the form
// "<inventor's name> invented the <invention>." (using projection !{...}).
String expression = "#root.inventions.![#root.name + ' invented the ' + #this + '.']";

// Evaluates to a list containing:
// "Nikola Tesla invented the Telephone repeater."
// "Nikola Tesla invented the Tesla coil transformer."
List<String> results = parser.parseExpression(expression)
		.getValue(context, tesla, List.class);

函数

可以通过注册用户定义的函数来扩展 SpEL,这些函数可以在表达式中使用 #functionName(…​) 语法进行调用。函数可以通过在 EvaluationContext 实现中使用 setVariable() 方法作为变量进行注册。

StandardEvaluationContext 还定义了 registerFunction(…​) 方法,为将函数作为 java.lang.reflect.Methodjava.lang.invoke.MethodHandle 进行注册提供了一种方便的方式。

由于函数在评估上下文中与变量共享一个公共的命名空间,因此必须小心确保函数名和变量名不重叠。

以下示例展示了如何使用 java.lang.reflect.Method 通过反射注册一个用户定义的函数以进行调用:

Method method = ...;

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("myFunction", method);

例如,考虑以下用于反转字符串的实用方法:

public abstract class StringUtils {

	public static String reverseString(String input) {
		return new StringBuilder(input).reverse().toString();
	}
}

可以按照以下示例注册和使用前面的方法:

ExpressionParser parser = new SpelExpressionParser();

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
		StringUtils.class.getMethod("reverseString", String.class));

// evaluates to "olleh"
String helloWorldReversed = parser.parseExpression(
		"#reverseString('hello')").getValue(context, String.class);

函数还可以作为 java.lang.invoke.MethodHandle 进行注册。如果 MethodHandle 目标和参数在注册之前已完全绑定,则此操作可能使用例更高效;但是,也支持部分绑定的句柄。

考虑 String#formatted(String, Object…​) 实例方法,该方法根据模板和可变数量的参数生成消息。

可以将 formatted 方法作为 MethodHandle 进行注册和使用,如下例所示:

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

MethodHandle mh = MethodHandles.lookup().findVirtual(String.class, "formatted",
		MethodType.methodType(String.class, Object[].class));
context.setVariable("message", mh);

// evaluates to "Simple message: <Hello World>"
String message = parser.parseExpression("#message('Simple message: <%s>', 'Hello World', 'ignored')")
		.getValue(context, String.class);

如上所述,还支持绑定 MethodHandle 并注册绑定的 MethodHandle。如果目标和所有参数都已绑定,则可能具有更高的性能。在这种情况下,SpEL 表达式中不需要任何参数,如下例所示:

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

String template = "This is a %s message with %s words: <%s>";
Object varargs = new Object[] { "prerecorded", 3, "Oh Hello World!", "ignored" };
MethodHandle mh = MethodHandles.lookup().findVirtual(String.class, "formatted",
		MethodType.methodType(String.class, Object[].class))
		.bindTo(template)
		.bindTo(varargs); //here we have to provide arguments in a single array binding
context.setVariable("message", mh);

// evaluates to "This is a prerecorded message with 3 words: <Oh Hello World!>"
String message = parser.parseExpression("#message()")
		.getValue(context, String.class);

Bean 引用

如果评估上下文已配置有 Bean 解析器,则可以使用 @ 符号从表达式中查找 Bean。以下示例展示了如何执行此操作:

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"something") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("@something").getValue(context);

要访问工厂 Bean 本身,应该使用 & 符号作为 Bean 名称的前缀。以下示例展示了如何执行此操作:

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// This will end up calling resolve(context,"&foo") on MyBeanResolver during evaluation
Object bean = parser.parseExpression("&foo").getValue(context);

三元运算符(If-Then-Else)

可以在表达式中使用三元运算符执行 if-then-else 条件逻辑。以下列表显示了一个最小示例:

String falseString = parser.parseExpression(
		"false ? 'trueExp' : 'falseExp'").getValue(String.class);

在这种情况下,布尔值 false 导致返回字符串值 ‘falseExp’。下面是一个更现实的例子:

parser.parseExpression("name").setValue(societyContext, "IEEE");
societyContext.setVariable("queryName", "Nikola Tesla");

expression = "isMember(#queryName)? #queryName + ' is a member of the ' " +
		"+ Name + ' Society' : #queryName + ' is not a member of the ' + Name + ' Society'";

String queryResultString = parser.parseExpression(expression)
		.getValue(societyContext, String.class);
// queryResultString = "Nikola Tesla is a member of the IEEE Society"

Elvis 运算符

Elvis 运算符是三元运算符语法的简化形式,用于 Groovy 语言。使用三元运算符语法时,通常需要重复一个变量两次,如下例所示:

String name = "Elvis Presley";
String displayName = (name != null ? name : "Unknown");

相反,可以使用 Elvis 运算符(因其与 Elvis 的发型相似而得名)。以下示例展示了如何使用 Elvis 运算符:

ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("name?:'Unknown'").getValue(new Inventor(), String.class);
System.out.println(name);  // 'Unknown'

SpEL 的 Elvis 运算符除了检查null 对象外,还会检查empty 字符串。因此,原始代码片段仅接近于模拟运算符的语义(它需要额外的 !name.isEmpty() 检查)。

以下列表显示了一个更复杂的示例:

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

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Nikola Tesla

tesla.setName("");
name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Elvis Presley

可以在表达式中使用 Elvis 运算符应用默认值。以下示例展示了如何在 @Value 表达式中使用 Elvis 运算符:

@Value("#{systemProperties['pop3.port'] ?: 25}")

如果定义了系统属性 pop3.port,则将注入该属性;否则,将注入默认值 25

安全导航运算符(Safe Navigation Operator)

安全导航运算符 (?) 用于避免 NullPointerException,该运算符来自 Groovy 语言。通常,当引用一个对象时,在访问该对象的方法或属性之前,可能需要验证该对象是否不为 null。为了避免这种情况,安全导航运算符会针对特定的空安全操作返回 null,而不是抛出异常。

当复合表达式中的特定null-safe操作的安全导航运算符计算结果为 null 时,复合表达式的其余部分仍将进行评估。

安全属性和方法访问

以下示例展示了如何使用安全导航运算符进行属性访问(?.)。

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

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan"));

// evaluates to "Smiljan"
String city = parser.parseExpression("placeOfBirth?.city")
		.getValue(context, tesla, String.class);

tesla.setPlaceOfBirth(null);

// evaluates to null - does not throw NullPointerException
city = parser.parseExpression("placeOfBirth?.city")
		.getValue(context, tesla, String.class);

安全导航运算符也适用于对象上的方法调用。

例如,如果未在上下文中配置 #calculator 变量,则表达式 #calculator?.max(4, 2) 将评估为 null。否则,将在 #calculator 上调用 max(int, int) 方法。

安全集合选择和投影(Safe Collection Selection and Projection)

Spring表达式语言通过以下操作符支持集合选择和集合投影的安全导航。

  • null-safe 选择:?.?
  • null-safe 选择第一个:?.^
  • null-safe 选择最后一个:?.$
  • null-safe 投影:?.!

下示例展示了如何在集合选择中使用安全导航运算符(?.?):

ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression = "members?.?[nationality == 'Serbian']";

// evaluates to [Inventor("Nikola Tesla")]
List<Inventor> list = (List<Inventor>) parser.parseExpression(expression)
		.getValue(context);

society.members = null;

// evaluates to null - does not throw a NullPointerException
list = (List<Inventor>) parser.parseExpression(expression)
		.getValue(context);

以下示例展示了如何在集合中使用“空安全选择第一个”操作符(?.^):

ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression =
	"members?.^[nationality == 'Serbian' || nationality == 'Idvor']";

// evaluates to Inventor("Nikola Tesla")
Inventor inventor = parser.parseExpression(expression)
		.getValue(context, Inventor.class);

society.members = null;

// evaluates to null - does not throw a NullPointerException
inventor = parser.parseExpression(expression)
		.getValue(context, Inventor.class);

以下示例展示了如何在集合中使用“空安全选择最后一个”操作符(?.$):

ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression =
	"members?.$[nationality == 'Serbian' || nationality == 'Idvor']";

// evaluates to Inventor("Pupin")
Inventor inventor = parser.parseExpression(expression)
		.getValue(context, Inventor.class);

society.members = null;

// evaluates to null - does not throw a NullPointerException
inventor = parser.parseExpression(expression)
		.getValue(context, Inventor.class);

以下示例展示了如何在集合投影中使用安全导航操作符(?.!):

ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);

// evaluates to ["Smiljan", "Idvor"]
List placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]")
		.getValue(context, List.class);

society.members = null;

// evaluates to null - does not throw a NullPointerException
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]")
		.getValue(context, List.class);

复合表达式中的空安全操作

当复合表达式中的某个空安全操作通过安全导航操作符评估为null时,复合表达式的其余部分仍会被评估。这意味着为了避免任何不希望的NullPointerException,必须在整个复合表达式中应用安全导航操作符。

给定表达式 #person?.address.city,如果 #personnull,安全导航操作符 (?.) 确保在尝试访问 #personaddress 属性时不会抛出异常。然而,由于 #person?.address 评估为 null,在尝试访问 nullcity 属性时将会抛出 NullPointerException。为了解决这个问题,你可以在复合表达式中整个应用空安全导航,如 #person?.address?.city。这个表达式会在 #person#person?.address 评估为 null 时安全地评估为 null

以下示例展示了如何在复合表达式中结合使用集合的“空安全选择第一个”操作符(?.^)和空安全属性访问(?.)。如果 membersnull,那么“空安全选择第一个”操作符(members?.^[nationality == 'Serbian'])的结果将评估为 null,而安全导航操作符(?.name)的额外使用确保整个复合表达式评估为 null,而不是抛出异常。

ExpressionParser parser = new SpelExpressionParser();
IEEE society = new IEEE();
StandardEvaluationContext context = new StandardEvaluationContext(society);
String expression = "members?.^[nationality == 'Serbian']?.name";

// evaluates to "Nikola Tesla"
String name = parser.parseExpression(expression)
		.getValue(context, String.class);

society.members = null;

// evaluates to null - does not throw a NullPointerException
name = parser.parseExpression(expression)
		.getValue(context, String.class);

集合选择(Collection Selection)

选择是一种强大的表达式语言功能,它允许从源集合的条目中进行选择,从而将其转换为另一个集合。

选择使用 .?[selectionExpression] 的语法。它过滤集合并返回包含原始元素子集的新集合。例如,选择使我们能够轻松地获取Serbian inventors的列表,如下所示:

List<Inventor> list = (List<Inventor>) parser.parseExpression(
		"members.?[nationality == 'Serbian']").getValue(societyContext);

对于数组和任何实现 java.lang.Iterable 或 java.util.Map 的对象,都支持选择。对于数组或 Iterable,选择表达式会对每个单独的元素进行评估。对于 Map,选择表达式会对每个 Map 条目(Java 类型 Map.Entry 的对象)进行评估。每个 Map 条目都将其keyvalue 作为可用于选择的属性进行访问。

给定一个存储在名为 #map 的变量中的 Map,以下表达式将返回一个新的 Map,它包含原始 Map 中那些值小于 27 的条目:

Map newMap = parser.parseExpression("#map.?[value < 27]").getValue(Map.class);

除了返回所有选定的元素外,你还可以只检索第一个或最后一个元素。要获取与选择表达式匹配的第一个元素,语法是 .^[selectionExpression]。要获取与选择表达式匹配的最后一个元素,语法是 .$[selectionExpression]

集合投影(Collection Projection)

投影允许一个集合驱动子表达式的评估,并且结果是一个新的集合。投影的语法是 .![projectionExpression]。例如,假设我们有一个inventor列表,但我们想要的是他们出生的城市列表。实际上,我们希望为发明家列表中的每个条目评估 placeOfBirth.city。以下示例使用投影来实现这一点:

// evaluates to ["Smiljan", "Idvor"]
List placesOfBirth = parser.parseExpression("members.![placeOfBirth.city]")
		.getValue(societyContext, List.class);

对于数组和任何实现 java.lang.Iterablejava.util.Map 的对象,都支持投影。当使用 Map 来驱动投影时,投影表达式会对 Map 中的每个条目(表示为 Java Map.Entry)进行评估。在 Map 上进行投影的结果是一个列表,该列表由对每个 Map 条目进行投影表达式评估的结果组成。

表达式模板(Expression Templating)

表达式模板允许将文本字面量与一个或多个评估块混合使用。每个评估块都用你定义的前缀和后缀字符进行分隔。一个常见的选择是使用 #{ } 作为分隔符,如下所示:

String randomPhrase = parser.parseExpression(
		"random number is #{T(java.lang.Math).random()}",
		new TemplateParserContext()).getValue(String.class);

// evaluates to "random number is 0.7038186818312008"

字符串是通过将文本字面量 'random number is ’ 与 { } 分隔符内表达式的评估结果连接起来进行评估的(在这种情况下,是调用 random() 方法的结果)。parseExpression() 方法的第二个参数是 ParserContext 类型。ParserContext 接口用于影响表达式的解析方式,以支持表达式模板功能。在前一个示例中使用的 TemplateParserContext 位于 org.springframework.expression.common 包中,是 ParserContext 的一个实现,默认情况下将前缀和后缀分别配置为 {}

示例中使用的类

本节列出了本章示例中使用的类。

Inventor

package org.spring.samples.spel.inventor;

import java.util.Date;
import java.util.GregorianCalendar;

public class Inventor {

	private String name;
	private String nationality;
	private String[] inventions;
	private Date birthdate;
	private PlaceOfBirth placeOfBirth;

	public Inventor(String name, String nationality) {
		GregorianCalendar c= new GregorianCalendar();
		this.name = name;
		this.nationality = nationality;
		this.birthdate = c.getTime();
	}

	public Inventor(String name, Date birthdate, String nationality) {
		this.name = name;
		this.nationality = nationality;
		this.birthdate = birthdate;
	}

	public Inventor() {
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getNationality() {
		return nationality;
	}

	public void setNationality(String nationality) {
		this.nationality = nationality;
	}

	public Date getBirthdate() {
		return birthdate;
	}

	public void setBirthdate(Date birthdate) {
		this.birthdate = birthdate;
	}

	public PlaceOfBirth getPlaceOfBirth() {
		return placeOfBirth;
	}

	public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
		this.placeOfBirth = placeOfBirth;
	}

	public void setInventions(String[] inventions) {
		this.inventions = inventions;
	}

	public String[] getInventions() {
		return inventions;
	}
}

PlaceOfBirth

package org.spring.samples.spel.inventor;

public class PlaceOfBirth {

	private String city;
	private String country;

	public PlaceOfBirth(String city) {
		this.city=city;
	}

	public PlaceOfBirth(String city, String country) {
		this(city);
		this.country = country;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String s) {
		this.city = s;
	}

	public String getCountry() {
		return country;
	}

	public void setCountry(String country) {
		this.country = country;
	}
}

Society

package org.spring.samples.spel.inventor;

import java.util.*;

public class Society {

	private String name;

	public static String Advisors = "advisors";
	public static String President = "president";

	private List<Inventor> members = new ArrayList<>();
	private Map officers = new HashMap();

	public List getMembers() {
		return members;
	}

	public Map getOfficers() {
		return officers;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public boolean isMember(String name) {
		for (Inventor inventor : members) {
			if (inventor.getName().equals(name)) {
				return true;
			}
		}
		return false;
	}
}
  • 18
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring表达式语言SpEL)是一种强大的表达式语言,用于在运行时计算表达式的值。SpEL可以用于配置文件、注解和代码中,支持对Bean的属性、方法和构造函数进行访问和操作,提供了很多常用的运算符和函数,是Spring框架中非常重要的一部分。 SpEL支持以下特性: 1. 引用Bean的属性和方法。可以使用“#”符号引用Bean的属性和方法,如“#user.name”表示引用名为user的Bean的name属性。 2. 调用静态方法和常量。可以使用“T()”关键字调用静态方法和常量,如“T(java.lang.Math).PI”表示引用Math类的PI常量。 3. 访问数组和集合。可以使用“[]”符号访问数组和集合,如“list[0]”表示访问名为list的集合的第一个元素。 4. 进行算术运算和比较运算。SpEL支持常见的算术运算和比较运算符,如“+”、“-”、“*”、“/”、“%”、“==”、“!=”、“<”、“>”等。 5. 定义变量和使用占位符。可以使用“#{}”定义变量和使用占位符,如“#{T(System).currentTimeMillis()}”表示定义一个名为currentTimeMillis的变量并赋值为当前时间的毫秒数。 6. 调用Bean的构造函数。可以使用“new”关键字调用Bean的构造函数,如“new java.util.Date()”表示调用java.util.Date类的无参构造函数。 SpEL可以在Spring的XML配置文件、@Value注解以及SpEL表达式注解中使用,可以方便地实现复杂的条件判断和动态计算。例如,可以使用SpEL表达式注解来实现@Scheduled注解的cron表达式的动态计算,或者在XML配置文件中使用SpEL表达式来实现Bean之间的依赖注入和条件配置等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值