10. Spring 表达语言(SpEL)

10.1 Introduction

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

尽管还有其他几种 Java 表达式语言可用,例如 OGNL,MVEL 和 JBoss EL,但 Spring Expression Language 的创建是为了向 Spring 社区提供一种受良好支持的表达式语言,该语言可用于该版本中的所有产品。Spring 投资组合。它的语言功能受 Spring 产品组合中项目的要求驱动,包括基于 Eclipse 的 Spring Tool Suite 中代码完成支持的工具要求。也就是说,SpEL 基于与技术无关的 API,允许在需要时集成其他表达语言实现。

虽然 SpEL 是 Spring 产品组合中表达评估的基础,但它并不直接与 Spring 绑定,可以独立使用。为了自成一体,本章中的许多示例都将 SpEL 当作一种独立的表达语言来使用。这需要创建一些自举基础结构类,例如解析器。大多数 Spring 用户将不需要处理这种基础结构,而只需要编写表达式字符串来进行评估。这种典型用法的一个示例是将 SpEL 集成到创建 XML 或基于 Comments 的 Bean 定义中,如表达式支持用于定义 bean 定义。部分所示。

本章介绍了表达语言,其 API 和语言语法的功能。在某些地方,Inventor 和 Inventor's Society 类用作表达评估的目标对象。这些类声明和用于填充它们的数据在本章末尾列出。

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

  • Literal expressions

  • 布尔运算符和关系运算符

  • Regular expressions

  • Class expressions

  • 访问属性,数组,列表,Map

  • Method invocation

  • Relational operators

  • Assignment

  • Calling constructors

  • Bean references

  • Array construction

  • Inline lists

  • Inline maps

  • Ternary operator

  • Variables

  • 用户定义的功能

  • Collection projection

  • Collection selection

  • Templated expressions

10.2 Evaluation

本节介绍 SpEL 接口的简单用法及其表达语言。完整的语言参考可以在Language Reference部分中找到。

以下代码介绍了 SpEL API,用于评估 Literals 字符串表达式“ Hello World”。

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

message 变量的值只是“ Hello World”。

您最可能使用的 SpEL 类和接口位于程序包org.springframework.expression及其子程序包spel.support中。

接口ExpressionParser负责解析表达式字符串。在此示例中,表达式字符串是由周围的单引号表示的字符串 Literals。接口Expression负责评估先前定义的表达式字符串。当分别调用parser.parseExpressionexp.getValue时,可以引发ParseExceptionEvaluationException两个异常。

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

作为方法调用的示例,我们在字符串 Literals 上调用concat方法。

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

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

作为调用 JavaBean 属性的示例,可以如下所示调用 String 属性Bytes

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 的构造函数,而不是使用字符串 Literals。

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属性或如何创建布尔条件:

// 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");
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"

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

10.2.1 EvaluationContext

评估表达式以解析属性,方法,字段并帮助执行类型转换时,将使用接口EvaluationContext。有两种现成的实现。

  • SimpleEvaluationContext-公开了 SpEL 基本语言功能和配置选项的子集,用于不需要全部 SpEL 语言语法范围且应进行有意义限制的表达式类别。示例包括但不限于数据绑定表达式,基于属性的过滤器等。

  • StandardEvaluationContext-pose 展示 SpEL 语言功能和配置选项的全部集合。您可以使用它来指定默认的根对象,并配置每个可用的评估相关策略。

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

  • 仅自定义PropertyAccessor(无反射)。

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

  • 读写的数据绑定属性。

Type conversion

默认情况下,SpEL 使用 Spring Core(org.springframework.core.convert.ConversionService)中可用的转换服务。此转换服务随附有许多用于常见转换的内置转换器,但也可以完全扩展,因此可以添加类型之间的自定义转换。此外,它还具有通用的关键功能。这意味着在表达式中使用泛型类型时,SpEL 将尝试进行转换以维护遇到的任何对象的类型正确性。

在实践中这意味着什么?假设使用setValue()的赋值被用来设置List属性。该属性的类型实际上是List<Boolean>。 SpEL 将认识到列表中的元素在放入列表之前需要转换为Boolean。一个简单的例子:

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

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

SimpleEvaluationContext context = SimpleEvaluationContext().create();

// false is passed in here as a string. SpEL and the conversion service will
// correctly recognize that it needs to be a Boolean and convert it

parser.parseExpression("booleanList[0]").setValue(context, simple, "false");

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

10.2.2 解析器配置

可以使用解析器配置对象(org.springframework.expression.spel.SpelParserConfiguration)配置 SpEL 表达式解析器。配置对象控制某些表达式组件的行为。例如,如果索引到数组或集合中并且指定索引处的元素是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.2.3 SpEL 编译

Spring Framework 4.1 包含一个基本的表达式编译器。通常对表达式进行解释,这样可以在评估过程中提供很大的动态灵 Active,但不能提供最佳性能。对于偶尔使用表达式,这很好,但是当与其他组件(如 Spring Integration)一起使用时,性能可能非常重要,并且不需要动态性。

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

对于这样的基本表达式:

someArray[0].someProperty.someOtherProperty < 0.1

其中涉及数组访问,一些属性取消引用和数字运算,因此性能提升会非常明显。在一个示例中,进行了 50000 次迭代的微基准测试,仅使用解释器进行评估需要 75 毫秒,而使用表达式的编译版本仅需要 3 毫秒。

Compiler configuration

默认情况下不打开编译器,但是有两种方法可以打开它。当 SpEL 用法嵌入到另一个组件中时,可以使用前面讨论的解析器配置过程来打开它,也可以通过系统属性来打开它。本节讨论这两个选项。

重要的是要理解,编译器可以在枚举(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);

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

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

Compiler limitations

使用 Spring Framework 4.1,就可以使用基本的编译框架。但是,该框架尚不支持编译每种表达式。最初的重点是可能在性能关键的上下文中使用的通用表达式。目前无法编译以下类型的表达式:

  • 涉及赋值的表达式

  • 依赖转换服务的表达式

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

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

将来将可以编译越来越多的表达类型。

10.3 Bean 定义中的表达式

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

10.3.1 XML 配置

可以使用如下所示的表达式来设置属性或构造函数-arg 值。

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

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

变量systemProperties是 sched 义的,因此您可以在表达式中使用它,如下所示。请注意,在这种情况下,您不必在 sched 义变量前加上#符号。

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

10.3.2Comments 配置

@ValueComments 可以放在字段,方法和方法/构造函数参数上以指定默认值。

这是设置字段变量默认值的示例。

public static class FieldValueTestBean

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

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

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

}

等效但使用属性设置器方法如下所示。

public static class PropertyValueTestBean

    private String defaultLocale;

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

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

}

自动装配的方法和构造函数也可以使用@ValueComments。

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;

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

    // ...
}

10.4 语言参考

10.4.1Literals 表达

支持的 Literals 表达式的类型为字符串,数值(int,实数,十六进制),布尔值和 null。字符串由单引号分隔。要将单引号本身放在字符串中,请使用两个单引号字符。

以下 Lists 显示了 Literals 的简单用法。通常,它们不会像这样孤立地使用,而是作为更复杂的表达式的一部分使用,例如在逻辑比较运算符的一侧使用 Literals。

ExpressionParser parser = new SpelExpressionParser();

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

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

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

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

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

数字支持使用负号,指数符号和小数点。默认情况下,使用 Double.parseDouble()解析实数。

10.4.2 属性,数组,列表,Map,索引器

使用属性引用进行导航很容易:只需使用句点来表示嵌套的属性值。 Inventor类,pupin 和 tesla 的实例填充了示例中使用的类部分中列出的数据。要向下导航并获取特斯拉的出生年份和普平的出生城市,请使用以下表达式。

// evals 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();
SimpleEvaluationContext context = SimpleEvaluationContext.create();

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

通过在方括号内指定 Literals 键值可以获取 Map 的内容。在这种情况下,因为用于“人员”Map 的键是字符串,所以我们可以指定字符串 Literals。

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

10.4.3 内联列表

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

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

{}本身表示一个空列表。出于性能原因,如果列表本身完全由固定的 Literals 组成,则将创建一个常量列表来表示表达式,而不是在每次求值时都构建一个新列表。

10.4.4 内嵌 Map

也可以使用{key:value}表示法在表达式中直接表达 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);

{:}本身就是一张空的 Map。出于性能原因,如果 Map 图本身由固定的 Literals 或其他嵌套的常量结构(列表或 Map)组成,则将创建一个常量图来表示该表达式,而不是在每次求值时都构建一个新的 Map 图。Map 键的引用是可选的,上面的示例未使用带引号的键。

10.4.5 阵列构造

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

当前,在构造多维数组时,不允许提供初始化程序。

10.4.6 Methods

使用典型的 Java 编程语法调用方法。您也可以在 Literals 上调用方法。还支持 Varargs。

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

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

Note

null进行大于/小于的比较遵循一个简单的规则:null在此处不视为任何内容(即,不视为零)。结果,任何其他值始终大于null(X > null始终为true),并且其他任何值都不小于零(X < null始终为false)。

如果您更喜欢数字比较,请避免使用基于数字的null比较,而建议使用零进行比较(例如X > 0X < 0)。

除标准关系运算符外,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);

Note

请注意原始类型,因为它们会立即装箱成包装类型,因此,按预期,1 instanceof T(int)的值为false,而1 instanceof T(Integer)的值为true

每个符号运算符也可以指定为纯字母等效项。这避免了所使用的符号对于嵌入表达式的文档类型(例如 XML 文档)具有特殊含义的问题。此处显示等效的文本:lt(<),gt(>),le(),ge(>=),eq(==),ne(!=),div(/)() )。这些不区分大小写。

Logical operators

支持的逻辑运算符是 and(或),and(不是)。它们的用法如下所示。

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

加法运算符可用于数字和字符串。减法,乘法和除法只能用于数字。支持的其他 math 运算符是模量(%)和指数幂(^)。强制执行标准运算符优先级。这些运算符如下所示。

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

10.4.8 Assignment

属性的设置是通过使用赋值运算符完成的。这通常会在对setValue的调用内完成,但也可以在对getValue的调用内完成。

Inventor inventor = new Inventor();
SimpleEvaluationContext context = SimpleEvaluationContext.create();

parser.parseExpression("Name").setValue(context, inventor, "Alexander Seovic2");

// alternatively

String aleks = parser.parseExpression(
        "Name = 'Alexandar Seovic'").getValue(context, inventor, String.class);

10.4.9 Types

特殊的T运算符可用于指定 java.lang.Class 的实例(* type *)。静态方法也使用此运算符调用。 StandardEvaluationContext使用TypeLocator查找类型,而StandardTypeLocator(可以替换)是在了解 java.lang 包的情况下构建的。这意味着对 Java.lang 中的类型的 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);

10.4.10 Constructors

可以使用 new 运算符调用构造函数。除基本类型和字符串(所有类型可以使用 int,float 等)外,所有其他类都应使用完全限定的类名。

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

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

10.4.11 Variables

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

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
SimpleEvaluationContext context = SimpleEvaluationContext.create();
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 始终引用根。

// 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();
SimpleEvaluationContext context = SimpleEvaluationContext.create();
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);

10.4.12 Functions

您可以通过注册可以在表达式字符串中调用的用户定义函数来扩展 SpEL。该功能通过EvaluationContext注册。

Method method = ...;

SimpleEvaluationContext context = SimpleEvaluationContext.create();
context.setVariable("myFunction", method);

例如,下面给出了一种用于反转字符串的 Util 方法:

public abstract class StringUtils {

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

然后可以注册上述方法并按以下方式使用:

ExpressionParser parser = new SpelExpressionParser();
SimpleEvaluationContext context = SimpleEvaluationContext.create();

context.setVariable("reverseString",
    StringUtils.class.getDeclaredMethod("reverseString", String.class));

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

10.4.13 Bean 引用

如果评估上下文已使用 bean 解析器配置,则可以使用(@)符号从表达式中查找 bean。

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = StandardEvaluationContext.create();
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 = StandardEvaluationContext.create();
context.setBeanResolver(new MyBeanResolver());

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

10.4.14 三元运算符(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 运算符的下一部分,以获取三元运算符的更短语法。

10.4.15Elvis 操作员

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(String.class);

System.out.println(name); // 'Unknown'

这是一个更复杂的示例。

ExpressionParser parser = new SpelExpressionParser();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
SimpleEvaluationContext context = SimpleEvaluationContext.create();

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

10.4.16 安全导航操作员

“安全导航”操作符用于避免使用NullPointerException且来自Groovy语言。通常,当您具有对对象的引用时,可能需要在访问对象的方法或属性之前验证其是否为 null。为了避免这种情况,安全的导航运算符将只返回 null 而不抛出异常。

ExpressionParser parser = new SpelExpressionParser();

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

SimpleEvaluationContext context = SimpleEvaluationContext.create();

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

Note

Elvis 运算符可用于在表达式中应用默认值,例如在@Value表达式中:

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

如果定义了系统属性pop3.port,否则将注入 25.

10.4.17 集合选择

选择是一种强大的表达语言功能,可让您通过从条目中进行选择来将某些源集合转换为另一源集合。

选择使用语法.?[selectionExpression]。这将过滤该集合并返回一个包含原始元素子集的新集合。例如,选择可使我们轻松获得塞尔维亚发明家的列表:

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

在列表和 Map 上都可以选择。在前一种情况下,针对每个单独的列表元素评估选择标准,而针对 Map,则针对每个 Map 条目(Java 类型Map.Entry的对象)评估选择标准。Map 条目的键和值可作为属性访问,以供选择中使用。

该表达式将返回一个新 Map,该 Map 由原始 Map 中 Importing 值小于 27 的那些元素组成。

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

除了返回所有选定的元素外,还可以仅检索第一个或最后一个值。要获得与选择匹配的第一个条目,语法是^[…],而要获得与匹配的最后选择的语法是$[…]

10.4.18 集合投影

投影允许集合驱动子表达式的求值,结果是一个新的集合。投影的语法为![projectionExpression]。通过示例最容易理解,假设我们有一个发明者列表,但想要他们出生的城市列表。实际上,我们希望为发明人列表中的每个条目评估“ placeOfBirth.city”。使用投影:

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

Map 也可以用于驱动投影,在这种情况下,将针对 Map 中的每个条目(表示为 Java Map.Entry)评估投影表达式。跨 Map 的投影结果是一个列表,其中包含针对每个 Map 条目的投影表达式的评估。

10.4.19 表达式模板

表达式模板允许将 Literals 文本与一个或多个评估块混合在一起。每个评估块都用您可以定义的前缀和后缀字符定界,通常的选择是使用#{ }作为定界符。例如,

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

// evaluates to "random number is 0.7038186818312008"

通过将 Literals 文本“随机数为”与评估#\ { }分隔符内的表达式的结果(在本例中为调用那个 random()方法的结果)进行连接来评估字符串。方法parseExpression()的第二个参数的类型为ParserContextParserContext接口用于影响表达式的解析方式,以支持表达式模板功能。 TemplateParserContext的定义如下所示。

public class TemplateParserContext implements ParserContext {

    public String getExpressionPrefix() {
        return "#{";
    }

    public String getExpressionSuffix() {
        return "}";
    }

    public boolean isTemplate() {
        return true;
    }
}

10.5 示例中使用的类

Inventor.java

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

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

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

}