RedCloud Help

4. Spring Expression Language (SPEL)

Spring表达式语言(简称SPEL )是一种功能强大的表达式语言,支持在运行时查询和操作对象图。该语言的语法与Unified EL相似,但提供了更多的功能,其中最主要的是方法调用和基本的字符串模板功能。 虽然还有其他几种java表达式语言,如OGNL、MVEL和JBoss EL等,单创建Spring表达式语言的目的是为Spring社区提供一种支持良好的表达式语言,可在Spring产品组合的所有产品中使用。器语言特点由Spring产品组合中的项目需求驱动,包括Eclipse Spring Tools中代码完成支持的工具需求。尽管如此,Spel基于与技术无关的API,可在需要时集成其他表达式语言实现。 虽然SPEL是Spring组合中表达式评估的基础,但它与Spring并无直接联系,可以独立使用。为了实现自包含,本章中的许多实例都将SPEL当做独立的表达式语言来使用。这就需要创建一些引导基础结构类,如解析器。大多数Spring用户无需处理这些基础结构,而只需编写表达式字符串进行评估即可。这种典型用法的一个例子就是Spel集成到创建Xml或基于注解的bean定义中,如定义Beand定义的表达式支持中所示. 本章介绍了表达式语言的特点,API和语言语法。有多处使用Inventor和Society类作为表达式评估的目标对象。本章末尾列出了这些类的声明和用于填充这些类的数据。 表达式语言支持以下功能:

  • 字面表达

  • 布尔运算和关系运算符

  • 正则表达式

  • 类表达式

  • 访问属性、数组、列表和地图

  • 方法调用

  • 关系运算符

  • 任务

  • 调用构造函数

  • bean引用

  • 数组构造

  • 内联列表

  • 内联Map

  • 三元运算符

  • 变量

  • 用户自定义功能

  • 集合预测

  • 集合选择

  • 模板表达式

4.1 Evaluation

本届介绍SPEL界面及其表达语言的简单使用方法。完整的语言参考资料可在语言参考中找到。 下面的代码介绍了SPELAPI,用于评估字面字符串表达式Hello World。

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

你最有可能实用SPEL类和接口位于org.springframework..expression软件包及其子软件包中,例如spel.support. ExpressionParse接口负责解析表达式字符串。在前面的示例中,表达式字符串是一个字符串字面量,由周围的单引号表示。Expression接口负责评估先前定义的表达式字符串。在调用parser.parseExpression和exp.getValue时,可能分别抛出ParseException和EvaluationException两个异常。 SpEL支持多种功能,如调用方法、访问属性和调用构造函数。 在下面的方法调用实例中,我们在字符串字面量上调用concat方法:

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

以下调用JavaBean属性的实例调用了string属性Bytes:

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

SpEL还支持使用标准圆点符号的嵌套属性,以及相应的属性值设置。还可以访问公共字段。 下面的实例展示了如何使用点符号获取字面长度:

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

如下利所示,可以调用字符串的构造函数,而不是使用字符日字面量:

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

注意通用方法的使用:public T getValue(Class desiredResultType) .使用该方法后,无需表达式的值转换为所需的结果类型。如果值不能被转换为T类型,或不能使用注册的类型转换器进行转换,则会抛出EvaluationException消息。 SpEL更常见的用法是提供一个表达式字符串,针对特定对象实例(称根对象)进行求值。下面的实例展示了如何从Inventor类的实例中检索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

4.1.1了解EvaluationContext

EvaluationContext接口用于在求值表达式时解析属性、方法或字段,并帮助执行类型转换。Spring提供了两种实现。

  • SimpleEvaluationContext:为不需要的SpEL语言语法切应受到有意义限制的表达式类别,公开基于SpEL语言功能和配置选项的自己。这方面的例子包括但不限于数据绑定表达式和基于属性的过滤器。

  • StandardEvaluationContext:提供全套的SpEL语言功能和配置选项。你可以用它来指定默认跟对象,并配置所有可用的评估相关策略。

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

  • 仅自定义PropertyAccessor(无反射)

  • 只读访问的数据绑定属性

  • 读写数据绑定属性

Type Conversion 类型转换

默认情况下,SpEL使用Spring core中提供的转换服务(org.springframework.core.convert.ConversionService).该转换服务为常见转换提供了许多内置转换器,但也具有完全可扩展性,因此你可以在类型间添加自定义转换。此外,你还具有泛型感知功能。这意味着,当你在表达式中使用泛型类型时,SpEL会尝试进行转换,以保持遇到的任何对象的类型正确。 这在实践中意味着什么?假设使用setValue()进行赋值,以设置List属性。该属性的类型实际上是List 。SpEL认为,在将list中的元素放入之前,需要将其转换为Boolean属性。下面的示例演示了如何进行转换:

class Simple { public List<Boolean> booleanList = new ArrayList<Boolean>(); } 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);

4.1.2解析器配置

可以使用解析器配置对象(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

4.1.3 SpEL编译

Spring Framework 4.1包含了一个基本的表达式编译器。表达式通常是解释型的,这在评估过程中提供了很大对的动态灵活性,单无法提供最佳性能。对于偶尔使用表达式的情况,这并无大碍,单当其他组件(如Spring Integration)使用表达式时,性能可能会变的非常重要,而且动态性也没有真正的需求。 SpEL编译器旨在满足一个需求。在评估过程中,编译器会生成一个java类,在运行时体现表达式的行为,并使用该类实现更快的表达式评估。由于缺乏围绕表达式的类型,编译器在执行编译时会使用在表达式的解释求值过程中收集的信息。例如,编译器并不能纯粹从表达式中知道属性引用的类型,单在第一次解释评估时,编译器就能知道它是什么类型。当然,如果各种表达式元素的类型随着时间的推移而发生变化,那么根据这些派生信息进行编译就会带来麻烦。因此,编译最适合类型信息不回在重复求值时法生产变化的表达式。 请看下面的基本表达式:

someArray[0].someProperty.someOtherProperty < 0.1

由于前面的表达式涉及数组访问、一些属性去引用和数值操作,因此性能提升非常明显。在一个迭代50000此的微型基准运行实例中,使用解释器评估需要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);

在指定编译器模式时,还可以指定一个类加载器(允许传递空值)。编译后的表达式将定义在一个子类加载器中,该类加载器根据所提供的任何类型创建。重要的是,如果制定了类加载器,要确保它能看到表达式求值过程中设计的所有类型。如果未指定类加载器,将使用默认类加载器(通常是表达式求值过程中运行的线程的上下文类加载器)。 配置编译器的第二种方法适用于SpEL嵌入其他组件的情况,这种情况下可能无法通过配置对象进行配置。在这种情况下,可以通过JVM系统属性(或通过SpringProperties机制)将spring.expression.compiler.mode属性设置为SpelCompilerMode枚举之一(Off、immediate或mixed)。

编译器限制

自SpringFramework 4.1以来,基本的编译器框架已经到位。不过,该框架不支持编译所有类型的表达式。最初的重点是可能在性能关键型上下文发中使用的常见表达式。一下几种表达式暂时无法编译:

  • 涉及赋值的表达式。

  • 依赖转换的表达式

  • 使用自定义解析器或访问器的表达式

  • 使用选择或投影的表达式

今后还将编译更多类型的表达式

4.2 Bean定义中的表达式

你可以使用SpEL表达式和基于xml或注解配置元数据来定义BeanDefinition实例。在这两种情况下,定义表达式的语法形式都是#{}。

4.2.1 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)以及用于访问运行环境的systemProperties和systemEnvironment(类型为Map< String,Object>)。 下面的示例显示了如何以SpEL变量的形式访问systemProperties Bean:

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

4.2.2 注解配置

要指定默认值,可在字段、方法或构造函数参数上添加@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; } }

下面的实例显示了一个属性设置器方法的等效方法:

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

自动注入方法和构造函数也可以使用@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; } // ... }

4.3 语言引用

本届介绍Spring表达式语言的工作原理。它覆盖一下主题:

  • 文字表达

  • 属性、数组、列表、map和索引器

  • 内联列表

  • 内联map

  • array构造

  • 方法

  • 操作员

  • types类型

  • constructors构造函数

  • variables变量

  • functions方法

  • bean references Bean引用

  • 三元运算符

  • the elvis Operator

  • safe Navigation Operator

4.3.1. 字面表达

SpEL支持以下类型的文字表达式。

  • strings字符串

  • 数值:整数(int或long),十六进制(int或long),实数(float或double)

  • 布尔值:true或false

  • null无效

字符串可以用单引号(')或双引号(")分割。要在单引号括起来的字符串字面量中包含单引号,请使用两个相邻的单引号字符。同样,要在双引号括起来的字符串字面量中包含双引号,请使用两个相邻双引号字符。 数字支持使用负号、指数符号和小数点。默认情况下,实数使用Double.parseDouble()进行解析。 下面列出了字面量的简单用法。通常,他们不会像这样单独使用,而是作为更复杂表达式的一部分,例如,将字面量用于逻辑比较运算符的一遍或作为方法的参数。

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

4.3.2属性、数组、列表、map和索引器

使用属性引用导航非常简单。为此,请使用句号来表示嵌套的属性值。Inventor类的pupin和tesla实例已用实例部分所用类中列出的数据填充。要“向下”浏览对象图并获取特斯拉的出生年份和普平的出生城市,我们使用了一下表达式:

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

如夏利所示,数组和列表的内容可以通过使用方括号符号来获取:

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

4.3.3 Inline List内联列表

你可以使用{}符号表达式中直接表达列表。

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

{}本身表示空list。处于性能考虑,如果列表本身完全由固定字面量组成,则会创建一个常量列表来表示表达式(而不是在每次求值时创建一个新列表)。

4.3.4 Inline Maps内联map

你还可以使用符号在表达式直接表达映射。下面的实例展示了如何做到这一点:

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

本身以为这一个空映射。处于性能考虑,如果映射本身是固定字面量或其他嵌套常量构造(列表或映射)组成,则会创建一个常量映射来表示表达式(而不是在每次求值时创建一个新映射)。应设置的引号是可选的(除非键包含句号(.) )。上面的实例没有使用带不友好的键。

4.3.5 Array Construction数组阵列

你可以使用熟悉的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);

目前,在构造多为数组不能提供初始化器。

4.3.6 Methods方法

你可以使用典型的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);

4.3.7 Operators操作员

Spring 表达式语言支持一下几种操作符:

  • Relational Operators 关系运算符

  • Logical Operators 逻辑运算符

  • Mathematical Operators 数学运算符

  • The Assignment Operators 赋值操作符

Relational Operators关系运算符

使用标准的运算符符号支持关系运算符(等于、不等于、小于、小于等于、大于、大于等于)。 下面列出了几种运算符实例:

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

除标准关系运算符外,SpEL还支持instanceof和基于正则表达式的matches运算符。下面列出了这两种运算符的实例:

// evaluates to false boolean falseValue = parser.parseExpression( "'xyz' instanceof T(Integer)").getValue(Boolean.class); // evaluates to true boolean trueValue = parser.parseExpression( "'5.00' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class); // evaluates to false boolean falseValue = parser.parseExpression( "'5.0067' matches '^-?\\d+(\\.\\d{2})?$'").getValue(Boolean.class);

每个符号运算符也可以指定为纯粹的字母等价物。这样可以避免在表达式嵌入的文档类型(如XML文档)中使用具有特殊含义的符号时出现问题。文本等价运算符有:

  • lt (<)

  • gt (>)大于

  • le (<=)小于等于

  • ge (>=)大于等于

  • eq (==)等于

  • ne (!=)不等于

  • div (/)除以

  • mod (%)取模

  • not (!) not (!)

所有文本操作符都不区分大小写。

Logical Operators逻辑运算符

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

Mathematical Operators 数学运算符

你可以在数字和字符上使用加运算符(+)。减法运算符(-)、乘法运算符(* )和除法运算符(/)只能用于数字。你还可以在数字上使用模运算符(%)和指数幂运算符(^)。操作符优先级执行标准。下面的示例显示了所使用的数学运算符:

// Addition int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 String testString = parser.parseExpression( "'test' + ' ' + 'string'").getValue(String.class); // 'test string' // Subtraction int four = parser.parseExpression("1 - -3").getValue(Integer.class); // 4 double d = parser.parseExpression("1000.00 - 1e4").getValue(Double.class); // -9000 // Multiplication int six = parser.parseExpression("-2 * -3").getValue(Integer.class); // 6 double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); // 24.0 // Division int minusTwo = parser.parseExpression("6 / -3").getValue(Integer.class); // -2 double one = parser.parseExpression("8.0 / 4e0 / 2").getValue(Double.class); // 1.0 // Modulus int three = parser.parseExpression("7 % 4").getValue(Integer.class); // 3 int one = parser.parseExpression("8 / 5 % 2").getValue(Integer.class); // 1 // Operator precedence int minusTwentyOne = parser.parseExpression("1+2-3*8").getValue(Integer.class); // -21

The Assignment Operator赋值操作符

要设置属性,请使用赋值操作符(=)。这通常在调用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);

4.3.8 Types类型

你可以使用特殊的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);

4.3.9 构造函数

你可以使用new操作符调用构造函数。除了位于java.lang包中的类型(Integer、Float、String等等),所有类型都应该使用完全限定的类名。下面的示例展示了如何使用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);

4.3.10 变量

你可以使用#variableName语法在表达式中引用变量。在EvaluationContext实现中使用setVariable方法可以设置变量。

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

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和#root变量:

// create an array of integers List<Integer> primes = new ArrayList<Integer>(); primes.addAll(Arrays.asList(2,3,5,7,11,13,17)); // create parser and set variable 'primes' as the array of integers ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess(); context.setVariable("primes", primes); // all prime numbers > 10 from the list (using selection ?{...}) // evaluates to [11, 13, 17] List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression( "#primes.?[#this>10]").getValue(context);

4.3.11 功能

你可以通过注册可在表达式字符串中调用的用户自定义函数来扩展SpEL。函数通过EvaluationContext注册。下面的实例展示了如何注册用户自定义函数:

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

例如,请看下面这个翻转字符串的应用程序方法:

public abstract class StringUtils { public static String reverseString(String input) { StringBuilder backwards = new StringBuilder(input.length()); for (int i = 0; i < input.length(); i++) { backwards.append(input.charAt(input.length() - 1 - i)); } return backwards.toString(); } }

然后,你就可以注册并使用前一种方法,如下例所示:

ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); context.setVariable("reverseString", StringUtils.class.getDeclaredMethod("reverseString", String.class)); String helloWorldReversed = parser.parseExpression( "#reverseString('hello')").getValue(context, String.class);

4.3.12 Bean Referencese

如果已为评估上下文配置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);

要访问工厂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);

4.3.13 三元操作符

你可以使用三元操作符在表达式中执行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"

有关三元运算符的更简短语法,请参见下一节ternary 运算符。

4.3.14 The Elvis Operator

Elvis 运算符是三元运算符语法的简写,在Groovy语言中使用。使用三元操作符语法时,通常需要重复变量两次,如下例所示:

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

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

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

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

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(null); name = parser.parseExpression("name?:'Elvis Presley'").getValue(context, tesla, String.class); System.out.println(name); // Elvis Presley

4.3.15 safe Navigation Operator

安全导航操作符用于避免NullPointerException,它来自Groovy语言。通常情况下,当你用于一个对象引用时,可能需要在访问该对象的方法或属性之前验证该引用是否为空。为了避免这种情况,安全导航操作符会返回null而不是抛出异常。下面的示例展示了如何使用安全导航操作符:

ExpressionParser parser = new SpelExpressionParser(); EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); Inventor tesla = new Inventor("Nikola Tesla", "Serbian"); tesla.setPlaceOfBirth(new PlaceOfBirth("Smiljan")); String city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class); System.out.println(city); // Smiljan tesla.setPlaceOfBirth(null); city = parser.parseExpression("placeOfBirth?.city").getValue(context, tesla, String.class); System.out.println(city); // null - does not throw NullPointerException!!!

4.3.16 Collection Selection

选择是一种强大的表达式语言功能,通过从源集合的条目中进行选择,可以将源集合转换为另一个集合。 选择使用.?[selectionExpression]语法。它过滤集合并返回一个包含原始元素子集的新集合。例如,通过选择,我们可以轻松获得塞尔维亚发明家的列表,如下例所示:

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

数组和实现java.lang.Iterable或java.util.map的任何内容都支持选择。对于列表或数组,选择标准针对每个单独的元素进行评估。每个地图条目都有key和value属性,可在选择中使用。 下面的表达式会返回一个新的映射,改映射由原始映射中条目小于27的元素组成:

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

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

4.3.17 Collection Projection

投影让一个集合驱动一个子表达式的求值,求值结果是一个新的集合。投影的语法是.![projectionExpression] 。例如,假设我们有一个发明家列表,但希望得到他们出生的城市列表。实际上,我们要对发明家列表中的每个条目评估"placeOfBirth.city" 。下面的示例使用投影来实现这一目的:

// returns ['Smiljan', 'Idvor' ] List placesOfBirth = (List)parser.parseExpression("members.![placeOfBirth.city]");

数组和实现java.lang.Iterable或java.util.Map的任何内容都支持投影。使用映射驱动投影时,投影表达式将针对映射中的每个条目(以Java Map.Entry表示)进行评估。在地图上进行投影的结果是一个列表,该列表由针对每个地图条目的投影表达式求值组成。

4.3.18 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() 方法的第二个参数是ParseContext类型。ParserContext接口用于影响表达式的解析方式,以支持表达式模板化功能。TemplateParserContext的定义如下:

public class TemplateParserContext implements ParserContext { public String getExpressionPrefix() { return "#{"; } public String getExpressionSuffix() { return "}"; } public boolean isTemplate() { return true; } }

4.4 实例中使用的类

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

ackage 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; } }
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; } }
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<Inventor>(); 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; } }
03 May 2025