Spring Framework 中文文档

4.3.21.RELEASE

10. Spring 表达语言(SpEL)

10.1 简介

Spring 表达式语言(简称 SpEL)是一种强大的表达式语言,支持在运行时查询和操作 object 图。语言语法类似于 Unified EL,但提供了额外的 features,最值得注意的是方法调用和基本的 string 模板功能。

虽然还有其他几种 Java 表达式语言,OGNL,MVEL 和 JBoss EL,但是,我们创建了 Spring 表达式语言,以便为 Spring 社区提供一种支持良好的表达式语言,可以在所有产品中使用。 Spring 组合。其语言 features 由 Spring 产品组合中的项目要求驱动,包括 eclipse 基于 Spring 工具套件中 code 完成支持的工具要求。也就是说,SpEL 基于技术不可知的 API,允许在需要时集成其他表达语言 implementation。

虽然 SpEL 是 Spring 组合中表达式 evaluation 的基础,但它并不直接与 Spring 绑定,可以单独使用。在_se 自包含中,本章中的许多示例都使用 SpEL,就像它是一种独立的表达式语言一样。这需要创建一些引导基础结构 classes,例如解析器。大多数 Spring 用户不需要处理此基础结构,而只会为 evaluation 创建表达式 strings。这种典型用法的一个示例是将_Spel 整合到创建 XML 或基于注释的 bean 定义中,如表达式支持定义 bean 定义。部分所示

本章介绍表达式语言的 features,其 API 及其语言语法。在几个地方,Inventor 和 Inventor 的 Society class 被用作表达式 evaluation 的目标 objects。这些 class 声明和用于填充它们的数据列在本章末尾。

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

  • 文字表达

  • Boolean 和关系 operators

  • 常用表达

  • Class 表达式

  • 访问 properties,数组,lists,maps

  • 方法调用

  • 关系运算符

  • 分配

  • 调用构造函数

  • Bean references

  • Array 建设

  • 内联列表

  • 内联 maps

  • 三元运营商

  • 变量

  • 用户定义的功能

  • 收集投影

  • 收藏品选择

  • 模板化的表达

10.2 评价

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

以下 code 引入了 SpEL API 来评估文字 string 表达式'Hello World'。

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

消息变量的 value 只是'Hello World'。

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

接口ExpressionParser负责解析表达式 string。在此 example 中,表达式 string 是由周围的单引号表示的 string 文字。接口Expression负责评估先前定义的表达式 string。当分别调用parser.parseExpressionexp.getValue时,可以抛出两个 exceptions,ParseExceptionEvaluationException

SpEL 支持各种 features,例如调用方法,访问 properties 和调用构造函数。

作为方法调用的示例,我们在 string 文字上调用concat方法。

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

消息的 value 现在是'Hello World!'。

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

ExpressionParser parser = new SpelExpressionParser();

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

SpEL 还支持使用标准点表示法 i.e 的嵌套 properties。 prop1.prop2.prop3 和 property 值的设置

也可以访问公共字段。

ExpressionParser parser = new SpelExpressionParser();

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

可以调用 String 的构造函数而不是使用 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)。使用此方法无需将表达式的 value 强制转换为所需的结果类型。如果 value 无法转换为T类型或使用已注册的类型转换器转换,则抛出EvaluationException

SpEL 的更常见用法是提供一个表达式 string,该表达式是针对特定的 object 实例(称为 root object)进行评估的。 example 显示如何从Inventor class 的实例检索name property 或创建 boolean 条件:

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

在评估表达式以解析 properties,方法,字段以及帮助执行类型转换时,将使用接口EvaluationContext。有两个 out-of-the-box implementations。

  • SimpleEvaluationContext - 为不需要 SpEL 语言语法的完整范围的表达式类别公开必要的 SpEL 语言 features 和 configuration 选项的子集,并且应该进行有意义的限制。示例包括但不限于数据绑定表达式,property-based 过滤器等。

  • StandardEvaluationContext - 公开完整的 SpEL 语言 features 和 configuration 选项。您可以使用它来指定默认的根 object,并配置每个可用的 evaluation-related 策略。

SimpleEvaluationContext旨在仅支持 SpEL 语言语法的子集。它排除了 Java 类型 references,构造函数和 bean references。它还需要在表达式中明确选择 properties 和方法的 level。默认情况下,create()静态工厂方法仅启用对 properties 的读访问权限。您还可以获取构建器以配置所需支持的精确级别,定位以下之一或某些组合:

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

  • read-only 访问的数据 binding properties。

  • 数据 binding properties 用于读写。

类型转换

默认情况下,SpEL 使用 Spring 核心(org.springframework.core.convert.ConversionService)中提供的转换服务。此转换服务附带了许多内置的 common 转换转换器,但也完全可扩展,因此可以添加类型之间的自定义转换。此外,它具有 key 功能,它是通用的。这意味着当在表达式中使用泛型类型时,SpEL 将尝试转换以维护它遇到的任何 objects 的类型正确性。

这在实践中意味着什么?假设使用setValue()的赋值用于设置List property。 property 的类型实际上是List<Boolean>。 SpEL 会识别列表中的元素在放入之前需要转换为Boolean。一个简单的 example:

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 解析器 configuration

可以使用解析器 configuration object(org.springframework.expression.spel.SpelParserConfiguration)配置 SpEL 表达式解析器。 configuration object 控制某些表达式组件的行为。对于 example,如果索引到 array 或集合并且指定索引处的元素是null,则可以自动创建元素。当使用由 property references 链组成的表达式时,这很有用。如果索引到 array 或列表并指定超出 array 或列表当前大小末尾的索引,则可以自动增大 array 或 list 以适应该索引。

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 包含一个基本的表达式编译器。表达式通常被解释为在 evaluation 期间提供了很多动态灵活性,但是没有提供最佳的性能。对于偶尔的表达式使用,这很好,但是当被其他组件使用时,例如 Spring Integration,performance 可能非常重要,并且不需要动态。

新的 SpEL 编译器旨在满足这一需求。编译器将在 evaluation 期间动态生成一个真正的 Java class,它体现了表达式行为并使用它来实现更快的表达式 evaluation。由于缺少表达式的 typing,编译器在执行编译时使用在表达式的解释评估期间收集的信息。例如,它不完全从表达式中知道 property reference 的类型,但在第一次解释 evaluation 期间,它将找出它是什么。当然,如果各种表达式元素的类型在 time 内发生变化,那么基于此信息的编译可能会导致以后出现问题。因此,编译最适合于在重复评估时类型信息不会改变的表达式。

对于这样的基本表达式:

someArray[0].someProperty.someOtherProperty < 0.1

这涉及 array 访问,一些 property derefencing 和数字操作,performance gain 可以非常明显。在一个 50000 次迭代的 example 微基准运行中,使用 interpreter 评估需要 75ms,使用表达式的编译 version 只需 3ms。

编译器 configuration

默认情况下,编译器未打开,但有两种方法可以打开它。可以使用前面讨论的解析器 configuration process 或当 SpEL 用法嵌入到另一个 component 中时通过系统 property 打开它。本节讨论这两个选项。

重要的是要理解编译器可以运行的几种模式,在 enum(org.springframework.expression.spel.SpelCompilerMode)中捕获。模式如下:

  • OFF - 编译器已关闭;这是默认值。

  • IMMEDIATE - 在立即模式下,表达式会尽快编译。这通常是在第一次解释 evaluation 之后。如果编译的表达式失败(通常是由于类型更改,如上所述),则表达式 evaluation 的调用者将收到 exception。

  • MIXED - 在混合模式下,表达式在 time 上以静默方式在解释和编译模式之间切换。经过一些解释后的运行,它们将切换到编译形式,如果编译后的表单出现问题(如类型更改,如上所述),表达式将自动再次切换回解释形式。稍后它可能会生成另一个已编译的表单并切换到它。基本上,用户在IMMEDIATE模式下获得的 exception 是在内部处理的。

存在IMMEDIATE模式,因为MIXED模式可能会导致具有副作用的表达式出现问题。如果编译后的表达式在部分成功后爆炸,则可能已经完成了影响系统 state 的事情。如果发生这种情况,调用者可能不希望它在解释模式下静默 re-run,因为表达式的一部分可能是 running 两次。

选择模式后,使用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)。编译表达式将在提供的任何提供的 child 类加载器中定义。重要的是要确保是否指定了类加载器,它可以看到表达式 evaluation process 中涉及的所有类型。如果指定了 none,则将使用默认的类加载器(通常是在表达式 evaluation 期间 running 的线程的 context 类加载器)。

配置编译器的第二种方法是在 SpEL 嵌入到某个其他 component 中时使用,并且可能无法通过 configuration object 进行配置。在这些情况下,可以使用 system property。 property spring.expression.compiler.mode可以设置为SpelCompilerMode enum 值之一(offimmediatemixed)。

编译器限制

使用 Spring Framework 4.1,基本编译 framework 已到位。但是,framework 还不支持编译每种表达式。最初的重点是可能在 performance critical context 中使用的 common 表达式。这些表达式无法在 moment 中编译:

  • 涉及转让的表达

  • 表达式依赖于转换服务

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

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

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

10.3 bean 定义中的表达式

SpEL 表达式可与 XML 或 annotation-based configuration 元数据一起用于定义BeanDefinition s。在这两种情况下,定义表达式的语法都是#{ <expression string> }形式。

10.3.1 XML configuration

可以使用表达式设置 property 或 constructor-arg value,如下所示。

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

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

变量systemProperties是预定义的,因此您可以在表达式中使用它,如下所示。请注意,您不必在此 context 中使用#符号为预定义变量添加前缀。

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

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

对于 example,您还可以通过 name 引用其他 bean properties。

<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.2 Annotation 配置

@Value annotation 可以放在字段,方法和 method/constructor 参数上,以指定默认的 value。

这是一个 example 来设置字段变量的默认 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;
    }

}

等效但在 property 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 annotation。

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.1 文字表达

支持的文字表达式的类型是 strings,数值(int,real,hex), boolean 和 null。 Strings 由单引号分隔。要将单引号本身放在 string 中,请使用两个单引号字符。

以下清单显示了 literals 的简单用法。通常,它们不会像这样单独使用,而是作为更复杂表达式的一部分使用,例如在逻辑比较 operator 的一侧使用文字。

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

Numbers 支持使用负号,指数表示法和 decimal 点。默认情况下,使用 Double.parseDouble()解析实 numbers。

10.4.2 Properties,Arrays,Lists,Maps,Indexers

使用 property references 进行导航很简单:只需使用句点来表示嵌套的 property value。 Inventor class,pupin 和 tesla 的实例填充了示例中使用的类部分中列出的数据。为了“向下”导航并获得特斯拉的出生年份和普平的出生城市,使用以下表达方式。

// evals to 1856
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context);

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

property 名称的第一个字母允许不区分大小写。使用方括号表示法获得数组和 lists 的内容。

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

maps 的内容是通过在括号内指定文字 key value 获得的。在这种情况下,因为 Officers map 的键是 strings,我们可以指定 string 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 内联列表

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

{}本身表示空列表。出于性能原因,如果列表本身完全由固定_lite 组成,则创建一个常量列表来表示表达式,而不是在每个 evaluation 上构建一个新列表。

10.4.4 内联 Maps

Maps 也可以使用{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);

{:}本身表示空 map。出于性能原因,如果 map 本身由固定的_lite 或其他嵌套的常量结构(lists 或 maps)组成,则创建一个常量 map 来表示表达式,而不是在每个 evaluation 上构建一个新的 map。引用 map 键是可选的,上面的示例不使用带引号的键。

10.4.5 Array 建筑

可以使用熟悉的 Java 语法构建数组,可选地提供初始化程序以在构造 time 时填充 array。

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

在构造 multi-dimensional array 时,目前不允许提供初始化程序。

10.4.6 方法

使用典型的 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 操作员

关系运算符

关系运营商;使用标准 operator 表示法支持等于,不等于,小于,小于或等于,大于等于或等于。

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

Greater/less-than 与null的比较遵循一个简单的规则:null在这里被视为空(i.e.不是零)。因此,任何其他 value 总是大于null(X > null总是true),并且没有其他 value 小于零(X < null总是false)。

如果您更喜欢数字比较,请避免 number-based null比较,以支持与零比较(e.g. X > 0X < 0)。

除了标准的关系 operators 之外,SpEL 还支持基于matches operator 的instanceof和正则表达式。

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

小心原始类型,因为它们立即被装箱到 wrapper 类型,因此1 instanceof T(int)计算为false1 instanceof T(Integer)计算为true,如预期的那样。

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

逻辑操作员

支持的逻辑 operators 是和,或,而不是。它们的用途如下所示。

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

数学操作员

添加 operator 可以在 numbers 和 strings 上使用。减法,乘法和除法只能用于 numbers。支持的其他数学运算符是模数(%)和指数幂(^)。标准 operator 优先级被强制执行。这些操作符如下所示。

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

通过使用赋值 operator 来设置 property。这通常在对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 类型

特殊的T operator 可用于指定 java.lang.Class(类型)的实例。使用此 operator 也可以调用静态方法。 StandardEvaluationContext使用TypeLocator查找类型,StandardTypeLocator(可以替换)是在了解 java.lang 包的情况下构建的。这意味着对 java.lang 中的类型的 T() references 不需要完全限定,但所有其他类型的 references 必须是。

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 构造函数

可以使用新的 operator 调用构造函数。完全限定的 class name 应该用于除基本类型和 String 之外的所有类型(可以使用 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 变量

可以使用语法#variableName在表达式中引用变量。使用EvaluationContext implementations 上的 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 并引用当前的 evaluation object(解析非限定 references 的对象)。始终定义变量#root 并引用根 context object。虽然#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 功能

您可以通过注册可在表达式 string 中调用的用户定义函数来扩展 SpEL。 function 通过EvaluationContext注册。

Method method = ...;

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

对于 example,给定一个反转 string 的实用方法如下所示:

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 references

如果 evaluation context 配置了 bean 解析器,则可以使用(@)符号)从表达式中查找 beans。

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 name 应该以(&)符号为前缀。

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)

您可以使用三元 operator 在表达式中执行 if-then-else 条件逻辑。最小的示例是:

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

在这种情况下,boolean false 导致返回 string value'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 operator 的下一节,了解三元 operator 的更短语法。

10.4.15 Elvis Operator

Elvis operator 是三元 operator 语法的缩写,用于Groovy语言。对于 example,使用三元 operator 语法通常需要重复两次变量:

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

相反,你可以使用 Elvis operator,以 Elvis'发型的相似性命名。

ExpressionParser parser = new SpelExpressionParser();

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

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

这是一个更复杂的 example。

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 安全导航操作员

安全导航 operator 用于避免NullPointerException并来自Groovy语言。通常,当您对 object 具有 reference 时,您可能需要在访问 object 的方法或 properties 之前验证它是否为 null。为了避免这种情况,安全导航 operator 将只返回 null 而不是抛出 exception。

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

Elvis operator 可用于在表达式 e.g 中应用默认值。在@Value表达式中:

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

这将 inject 一个系统 property pop3.port如果它被定义,或者如果没有,则为 25。

10.4.17 收藏品选择

Selection 是一种功能强大的表达式语言 feature,允许您通过从其条目中进行选择将某些源集合转换为另一个源集合。

选择使用语法.?[selectionExpression]。这将过滤集合并 return 包含原始元素子集的新集合。例如,选择将使我们能够轻松获得塞尔维亚发明家名单:

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

可以在 lists 和 maps 上进行选择。在前一种情况下,针对每个单独的列表元素评估选择标准,而针对 map,针对每个 map 条目评估选择标准(类型Map.Entry的对象)。 Map 条目的 key 和 value 可以作为 properties 访问,以便在选择中使用。

此表达式将_return 一个新的 map,该 map 由原始 map 的那些元素组成,其中条目 value 小于 27。

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

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

10.4.18 收集投影

Projection 允许集合驱动 sub-expression 的 evaluation,结果是一个新的集合。投影的语法是![projectionExpression]。最容易理解的是 example,假设我们有一个发明者列表,但想要他们出生的城市列表。实际上,我们想要为发明人列表中的每个条目评估“placeOfBirth.city”。使用投影:

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

map 也可用于驱动投影,在这种情况下,投影表达式将根据 map 中的每个条目进行计算(表示为 Java Map.Entry)。跨 map 的投影结果是一个列表,其中包含对每个 map 条目的投影表达式的 evaluation。

10.4.19 表达式模板

表达式模板允许将文字文本与一个或多个 evaluation 块混合。每个 evaluation 块都用您可以定义的前缀和后缀字符分隔,common 选择是使用#{ }作为分隔符。例如,

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

// evaluates to "random number is 0.7038186818312008"

string 是通过将文本文本'random number is'与评估#{}分隔符内表达式的结果进行连接来计算的,在这种情况下是调用 random()方法的结果。方法parseExpression()的第二个参数是ParserContext类型。 ParserContext接口用于影响在 order 中解析表达式的方式,以支持表达式模板功能。 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;
    }

}
Updated at: 5 months ago
9.8.4. Spring MVC 3 验证Table of content11. 使用 Spring 进行面向对象编程