8. Spring 表达式语言 (SpEL)

8.1 介绍

Spring表达式语言(简称SpEl)是一个支持查询和操作运行时对象导航图功能的强大的表达式语言. 它的语法类似于传统EL,但提供额外的功能,最出色的就是函数调用和简单字符串的模板函数。

尽管有其他可选的 Java 表达式语言,如 OGNL, MVEL,JBoss EL 等等,但 Spel 创建的初衷是了给 Spring 社区提供一种简单而高效的表达式语言,一种可贯穿整个 Spring 产品组的语言。这种语言的特性应基于 Spring 产品的需求而设计。

虽然SpEL引擎作为Spring 组合里的表达式解析的基础 ,但它不直接依赖于Spring,可独立使用。为了整合,许多在本章使用SpEL例子就好像它是一个独立的表达式语言。这就需要创建一些引导 如解析器这样的基础构造类。大多数Spring用户将不再需要处理这些基础构建,而是仅将作者表达的字符串进行解析。一个传统的使用例子是集成SpEL去创建XML或者定义Bean的注解,可以选择这里看到 表达式支持定义bean.

本章讲介绍SpEL的API,其语言语法的特点。在几个地方,Inventor和Inventor’s Society 类被用做表达式解析的目标对象 。 这些类声明和使用数据一直贯穿本章结尾。

8.2 功能概述

表达式语言支持以下功能

  • 文字表达式
  • 布尔和关系运算符
  • 正则表达式
  • 类表达式
  • 访问 properties, arrays, lists, maps
  • 方法调用
  • 关系运算符
  • 参数
  • 调用构造函数
  • Bean引用
  • 构造Array
  • 内嵌lists
  • 内嵌maps
  • 三元运算符
  • 变量
  • 用户定义的函数
  • 集合投影
  • 集合筛选
  • 模板表达式

8.3 使用Spring的表达接口 表达式求值

本节介绍了简单的使用SpEL表达语言。 完整的语言参考可以在语言参考一节中找到

下面的代码使用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负责解析之前被定义的字符串表达式

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

作为方法调用的一个例子,我们调用字符串的“CONCAT”的方法。

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

结果将是 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();

字符串的构造函数的调用被一个巧妙的字符代替了。

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比较常见的用途是针对一个特定的对象实例(称为root object)提供被解析的表达式字符串. 这里有两种选择and which to choose depends on whether the object against which the expression is being evaluated will be changing with each call to evaluate the expression. 在下面的例子中, 我们检索一个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");

EvaluationContext context = new StandardEvaluationContext(tesla);
String name = (String) exp.getValue(context);

在最后一行,该字符串变量name的值将被设定为“Nikola Tesla”。 类StandardEvaluationContext是可以指定哪些对象的“name” 属性将被解析。如果root object不太可能改变. ,就可以简单地在评估上下文中设置一次。如果root object反复变化 ,它可以在每次调用getValue,如 接下来的例子说明:

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

在这种情况下,inventor tesla已直接应用到getValue和 表达式计算基础架构创建和管理一个默认的解析环境 在内部 - 它不要求再次解析。

StandardEvaluationContext是相对昂贵的构造和在重复 使用它建立缓存的状态,使得后续的解析将会变得更快. 出于这个原因,它将尽可能的更好的缓存和重用这些对象, 而不是建立一个新的每个表达式求值。

在某些情况下,它可以是理想的使用配置解析上下文,但仍然 在每次调用getValue提供不同的root object。 getValue允许既要 在同一个调用中指定。在这些情况下对root object通过调用要考虑 到覆盖任何(这可能为空)在解析范围内的指定。

[注意]

在SpEL的独立使用的时候,需要创建parser,parse expressions, 同时可能需要提供解析的context和root context object。然而,更常见的 用法是只提供一个SpEL表达式字符串作为配置文件的一部分, 例如,对于Spring的bean或Spring Web Flow的定义。在这种情况下,解析器 求值的context,root object和所有预定义变量都设置了隐式, 没有什么要用户去指定了,除了声明表达式.

作为最后一个例子,使用了一个boolean运算符去调用 inventor object 在前面的例子中。

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

8.3.1 EvaluationContext 接口

当计算表达式解析properties, methods, fields,并帮助执行类型转换, 使用接口EvaluationContext 这是一个开箱即用的实现, StandardEvaluationContext,使用反射来操纵对象, 缓存java.lang.reflectMethodField,和Constructor实例 提高性能。

StandardEvaluationContext是你可以指定root object通过使用 setRootObject()或传递root object到构造函数. 你也可以指定变量和函数 使用方法'的setVariable()和`registerFunction()的表达式。 变量和函数的使用将在变量中介绍 ,同时 函数. StandardEvaluationContext也是在那里你可以自定义的注册 ConstructorResolvers, MethodResolvers, and PropertyAccessors to extend how SpEL evaluates expressions. 请参见这些类的Javadoc获得更多信息。

类型转换

默认情况下,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);

StandardEvaluationContext simpleContext = new StandardEvaluationContext(simple);

// 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(simpleContext, "false");

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

8.3.2 解析器配置

用一个parser configuration object去配置SpEL解析器是可能的, (org.springframework.expression.spel.SpelParserConfiguration).配置 对象控制的一些表达组件的行为。例如,如果数据为 索引到指定索引处的数组或集合的元素是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表达式编译器的行为。

8.3.3 SpEL 编译

Spring Framework 4.1 包含了一个基本的表达式编译器. 表达式通常 解释其执行过程中提供了大量的动态灵活性,但 不提供最佳性能。对于偶尔使用的表达 这是好的,而是由其他组件,如Spring集成使用时, 性能是非常重要的,并没有为活力提供真正的需要。

新使用SpEL编译旨在解决这一需要。然后该 编译器将执行这体现了中动态生成一个真正的Java类 表达行为,并用它来实现更快的表达 式执行。由于缺乏各种表达式编译器 使用过程中的一个评估收集的评价的信息 当执行编译的表达。例如,它不知道的类型 参考表达,但在第一属性参考 解释执行会发现它是什么。当然,基于该 编译这些信息可能会造成的麻烦后,如果类型 各种表达元件随着时间而改变。出于这个原因汇编 是最适合返回执行不会改变其表达式类型的信息。

对于基本的表达是这样的:

someArray [0] .someProperty.someOtherProperty <0.1

其中涉及数组访问,部分属性引用和数字运算,性能 增益可以很明显的。在50000迭代一个例子微基准来看,它是 使用了75ms用来执行翻译,而仅仅3ms编译表达式的version。

=====编译器配置

编译器默认是并未开启的,但有两种方式打开 它。它被打开用parser configuration process 或者 通过系统属性将SpEL使用嵌入另一个组件中。本节 讨论这两个选项。

重要的是要明白,编译器可工作在几个模式下,查看详细可以用过一个enum (org.springframework.expression.spel.SpelCompilerMode). 模式如下:

  • OFF - 编译器被关闭;这是默认的。
  • IMMEDIATE - 在直接模式下,表达式尽快编制。 这是一个典型的首个编译选项。如果编译错误的表达式 (通常是由于一个类型变化,如上面所描述的)调用者将会得到一个异常。
  • MIXED - 在混合模式下,随着时间的推移,表达式默默地解释和编译之间切换。 经过解释运行的一些数字后,他们就会切换去编译源码 ,如果出现问题,编译形式(如一种变化,如 如上所述),那么表达式将自动切换回解释形式 。一段时间后,可能产生另一种形式的编制,并切换到它。基本上 相比用户IMMEDIATE模式,不同之处在于对于异常的处理,混合模式是隐式的(原话是: Basically the exception that the user gets in IMMEDIATE mode is instead handled internally。)。

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)之一。

编译器限制

随着Spring框架4.1的基本编制框架到位。然而,该框架 还没有支持编译每一种表情式。最初的重点一直是共同的表达 有可能在性能关键上下文中使用。这些种类的表达不能被编译 这些情况:

  • 涉及赋值表达式
  • 依托转换服务表达式
  • 使用自定义解析器或访问表达式
  • 使用选择或投影表达式

未来将支持越来越多类型的表达式。

8.4 定义bean的beandef表达支持

SpEL表达式可以与XML或基于注释的配置元数据使用 定义BeanDefinitions。在这两种情况下,以定义表达式语法的 形式#{<表达式字符串>}

8.4.1 基于XML的配置

一个属性或构造带参数的值可以使用表达式如下所示进行设置。

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

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

变量systemProperties是预定义的,所以你可以在你的表达式使用 如下所示。请注意,您不必用``#前缀的预定义变量 符号于该上下文。

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

8.4.2 基于注解的配置

@ Value注解可以放在字段,方法和方法/​​构造 参数里,以指定默认值。

这里是一个例子,设置一个字段变量的缺省值。

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

}

等效的属性setter方法​​如下所示。

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

}

自动装配方法和构造也可以使用@ 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;

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

    // ...
}

8.5 语言参考

8.5.1 文字表达式

支持文字表达的类型是字符串,日期,数值(int, real,十六进制),布尔和空。字符串使用单引号分隔。一个 单引号本身在字符串中使用两个单引号字符表示。下面的列表 显示文字的简单用法。通常,它们将不被使用在隔离像这样, 但作为一个更复杂的表达式的一部分,例如,使用一个文字上的一侧 逻辑比较操作符。

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

8.5.2 Properties, Arrays, Lists, Maps, Indexers

用属性引用引导很简单:只要用一个.表示嵌套 属性值。实现Inventor类, pupin和tesla, 被添加 被添加。在章节classes的例子. 使用表达式引导 "down" 同时获取 Tesla’s 出生年 和 Pupin’s 出生城市

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

// Inventions Array
StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);

// evaluates to "Induction motor"
String invention = parser.parseExpression("inventions[3]").getValue(
        teslaContext, String.class);

// Members List
StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);

// evaluates to "Nikola Tesla"
String name = parser.parseExpression("Members[0].Name").getValue(
        societyContext, String.class);

// List and Array navigation
// evaluates to "Wireless communication"
String invention = parser.parseExpression("Members[0].Inventions[6]").getValue(
        societyContext, String.class);

maps的内容由内指定的key值获得. 在这种情况下,因为对于人员的键映射是字符串,我们可以指定 字符串。

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

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

{}本身意味着空列表。出于性能的考虑,如果列表本身 完全由固定面值的则恒定列表被创建以代表 表达,而不是建立一个新的列表上的每个都执行。

8.5.4 内联maps

{key:value}记号的maps也可以使用表达式直接表示。

// 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本身是由 固定的文字或其他嵌套结构不变的(list或者map),那么一个恒定的map创建 代表表达,而不是建立新map每次去执行。使用.引用map的key 是可选的,上面的例子并没有使用.引用key。

8.5.5 array构造

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

即未被初始化的多维数组也可以被构造。

8.5.6 方法

方法被调用通过java典型的编程语法实现,您也可以调用方法 在表示式当中。可变参数也支持。

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

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

8.5.7 运算符

关系运算符

关系运算符;等于,不等于,小于,小于或等于,大于, 和大于或等于使用标准算子表示法的支持。

// 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(int)").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 (!). 这些都是不区分大小写。

逻辑运算符

所以支持的逻辑运算符 and, or, and 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);

数学运​​算符

加法运算符可以用于数字和字符串。减法,乘法 和除法只能在数字被使用。支持其他数学运算符 模量(%)和指数幂(^)。标准的运算符优先级执行。 这些 运算符展示在下文。

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

8.5.8 赋值

设置一个属性是通过使用赋值操作完成。这通常是 调用setValue,但也可以在调用getValue内完成。

Inventor inventor = new Inventor();
StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor);

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

// alternatively

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

8.5.9 类型

T操作可以被用来指定安装一个java.lang.ClassClass (the type). 静态方法也可以使用该运算符调用。然后该 StandardEvaluationContext使用TypeLocator找到类型和 StandardTypeLocator(可替换)是建立与的理解 java.lang package. 这意味着T()引用内的java.lang类型不需要是 完全限定,但所有其他类型的引用必须。

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

8.5.10 构造

构造函数可以使用new运算符调用。所有地方的类名应该是符合要求的, ,除了原语类型和字符串(其中整数,浮点,等等,都可以 使用)。

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

8.5.11 变量

变量可以在使用语法#variableName表达引用。变量 使用在StandardEvaluationContext方法的setVariable设置。

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
StandardEvaluationContext context = new StandardEvaluationContext(tesla);
context.setVariable("newName", "Mike Tesla");

parser.parseExpression("Name = #newName").getValue(context);

System.out.println(tesla.getName()) // "Mike Tesla"

该#this 和 #root变量

变量#this 始终定义和指向的是当前的执行对象 (不支持对其中不合格的引用解析)。变量#root总是 定义和指向root context object。虽然#this可能作为表达式的一些组件被执行 ,但#root总是指 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();
StandardEvaluationContext context = new StandardEvaluationContext();
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);

8.5.12 函数

您可以通过注册,可以在该调用用户自定义函数扩展SpEL 表达式字符串。该函数注册到'StandardEvaluationContext`使用 该方法。

public void registerFunction(String name, Method m)

引用一个Java方法提供了函数的实现。举个例子 一个实用的方法来扭转字符串如下所示。

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();
StandardEvaluationContext context = new StandardEvaluationContext();

context.registerFunction("reverseString",
    StringUtils.class.getDeclaredMethod("reverseString", new Class[] { String.class }));

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

8.5.13 bean引用

如果解析上下文已经配置,那么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);

8.5.14 三元运算符(IF-THEN-ELSE)

您可以使用三元运算符内部执行的if-then-else条件逻辑 的表达。一个最简单的例子是:

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

在这种情况下,在返回字符串值“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 运算符 使用一个更短的三元运算符语法。

8.5.15 Elvis操作符

Elvis操作符是三元运算符语法的缩短,并用于在 Groovy语言。 与三元运算符的语法,你通常要重复变量两次, 示例:

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

取而代之,你可以使用Elvis操作符,命名灵感来自猫王的发型风格。

ExpressionParser parser = new SpelExpressionParser();

String name = parser.parseExpression("null?:'Unknown'").getValue(String.class);

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

这里是一个更复杂的例子。

ExpressionParser parser = new SpelExpressionParser();

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

String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);

System.out.println(name); // Nikola Tesla

tesla.setName(null);

name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, String.class);

System.out.println(name); // Elvis Presley

8.5.16 安全导航运算符

安全导航操作符是用来避免'NullPointerException`和来自 该http://groovy.codehaus.org/Operators#Operators-SafeNavigationOperator(%3F.)[Groovy] 语言。通常情况下,当你有一个参考的对象,你可能需要验证 它不是访问方法或对象的属性之前空。为了避免这种情况,该 安全航行运算符将简单地返回空代替抛出的异常。

ExpressionParser parser = new SpelExpressionParser();

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

StandardEvaluationContext context = new StandardEvaluationContext(tesla);

String city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);
System.out.println(city); // Smiljan

tesla.setPlaceOfBirth(null);

city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, String.class);

System.out.println(city); // null - does not throw NullPointerException!!!

[注意]

Elvis操作符可用于应用中的表达式的默认值,例如在一个 @ Value表达式:

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

如果它不存在,那么将定义为25

8.5.17 集合选择

选择是一个强大的表达式语言功能,他允许你转换一些 源集合到另一个通过其条目选择。

选择使用语法?[selectionExpression].这将过滤收集和 返回一个包含原有元素的子集的新的集合。例如, 选择使我们能够轻松地获得Serbian inventors的列表:

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

选择可以被使用在lists或者maps集合当中。在前者的情况下,选择 标准执行对每个列表元素,同时针对map 选择定义的操作将会对map中的每个key执行。 (对象类似‘Map.Entry’) Map entries have their key and value accessible as properties for use in the selection。

下面这个例子将返回由原始映射(其中条目值小于27)所取得的这些元素的新map。

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

除了返回所有选定的元素,也可以用来获取 第一或最后一个值。以获得第一条目相匹配的选择的语法是 ^ [...]而获得最后一个匹配选择语法是$ [...].

8.5.18 集合投影

投影允许集合驱动子表达式和解析 生成一个新的集合。语法投影![projectionExpression]. 多数 功能通过实例容易理解,假设我们有Inventor list,希望得到 他们出生的城市。有效的方式是我们要使用“placeOfBirth.city”解析 在Inventor 中的每个条目。使用投影:

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

一个map也可以用于驱动投影。 在这种情况下,投影表达式 将解析map中的每一个元素(作为Java Map.Entry方法的一个代理)。 通过投影一个map将获得一个由投影表达式遍历每个元素所得到的list。

8.5.19 表达模板

表达式模板允许文字文本与一个或多个解析块的混合。 你可以每个解析块分隔前缀和后缀的字符, 当然,常见的选择是使用#{}作为分隔符。例如,

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”与 计算表达式的#{}定界符获取的结果,在此情况下的结果 中调用一个随机()方法。第二个参数的方法parseExpression() 是类型ParserContext的。在ParserContext接口用于影响如何 表达被解析,以便支持所述表达模板的功能。 的TemplateParserContext的定义如下所示。

public class TemplateParserContext implements ParserContext {

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

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

    public boolean isTemplate() {
        return true;
    }
}

8.6 Classes used in the examples

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

}