【翻译 Spring 5.0.4.RELEASE】4. Spring Expression Language (SpEL)

4. Spring Expression Language (SpEL)

4.1. Introduction

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

虽然还有其他几种可用的Java表达式语言,OGNL,MVEL和JBoss EL等等,Spring表达式语言的创建旨在为Spring社区提供单一支持的表达式语言,该语言可用于所有产品中春天的投资组合。它的语言特性由Spring产品组合中的项目需求驱动,包括基于Eclipse的Spring Tool Suite中代码完成支持的工具需求。也就是说,SpEL基于技术不可知的API,允许在需要时集成其他表达式语言实现。

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

本章介绍表达式语言,其API以及其语言语法的特性。在一些地方,Inventor和Inventor的Society类被用作表达评估的目标对象。这些类声明和用于填充它们的数据在本章最后列出。

4.2. Feature overview

表达式语言支持以下功能

  • 文字表达
  • 布尔和关系运算符
  • 常用表达
  • 类表达式
  • 访问属性,数组,列表,地图
  • 方法调用
  • 关系运算符
  • 分配
  • 调用构造函数
  • Bean引用
  • 阵列构建
  • 内联列表
  • 内联地图
  • 三元运营商
  • 变量
  • 用户定义的功能
  • 集合投影
  • 集合选择
  • 模板化表达式

4.3. Expression evaluation using Spring’s Expression interface

本节介绍简单使用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负责评估以前定义的表达式字符串。 当分别调用parser.parseExpression和exp.getValue时,可能会抛出两个异常,ParseException和EvaluationException。

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

可以调用String的构造函数,而不是使用字符串文字。

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属性。

// 创建并设置日历
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// 构造函数的参数是名字,生日和国籍。
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);

在最后一行中,字符串变量名称的值将被设置为“Nikola Tesla”。 类StandardEvaluationContext是您可以指定将针对哪个对象“名称”属性进行评估的地方。 如果根对象不太可能改变,这是一种使用的机制,它可以简单地在评估上下文中设置一次。 如果根对象可能会重复更改,则可以在每次调用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);

在这种情况下,发明者tesla直接提供给getValue,表达式评估基础架构在内部创建和管理默认评估上下文 - 它不需要提供。

StandardEvaluationContext的构建相对昂贵,并且在重复使用期间,它建立缓存状态,使得后续表达式评估能够更快地执行。 为此,最好在可能的地方缓存和重用它们,而不是为每个表达式评估构建一个新的表达式。

在某些情况下,可能需要使用配置的评估上下文,但仍然在每次调用getValue时提供不同的根对象。 getValue允许在同一个调用中指定两者。 在这些情况下,传递给调用的根对象将被视为覆盖评估上下文中指定的任何(可能为null)。

在独立使用SpEL时,需要创建解析器,解析表达式,并可能提供评估上下文和根上下文对象。 然而,更常见的用法是只提供SpEL表达式字符串作为配置文件的一部分,例如Spring bean或Spring Web Flow定义。 在这种情况下,解析器,评估上下文,根对象和任何预定义的变量都是隐式设置的,要求用户除了表达式以外不指定任何内容。

作为最后的介绍性示例,在前面的示例中使用Inventor对象显示了使用布尔运算符。

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

4.3.1. The EvaluationContext interface

评估表达式以解析属性,方法,字段并帮助执行类型转换时,将使用接口EvaluationContext。开箱即用的实现StandardEvaluationContext使用反射来操纵对象,缓存java.lang.reflect.Method,java.lang.reflect.Field和java.lang.reflect.Constructor实例以提高性能。

StandardEvaluationContext是您可以通过setRootObject()方法或者将根对象传递给构造函数来指定根对象的位置。您还可以使用方法setVariable()和registerFunction()指定将在表达式中使用的变量和函数。变量和函数的使用在变量和函数的语言参考章节中描述。 StandardEvaluationContext也是您可以注册自定义的ConstructorResolvers,MethodResolvers和PropertyAccessors以扩展SpEL如何评估表达式的地方。有关更多详细信息,请参阅这些类的javadoc。

Type conversion

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

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

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

4.3.2. Parser configuration

可以使用解析器配置对象(org.springframework.expression.spel.SpelParserConfiguration)来配置SpEL表达式解析器。 配置对象控制一些表达式组件的行为。 例如,如果索引到数组或集合中,并且指定索引处的元素为空,则可以自动创建该元素。 当使用由一系列属性引用组成的表达式时,这非常有用。 如果索引到数组或列表中并指定超出数组或列表当前大小末尾的索引,则可以自动增大数组或列表以适应该索引。

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表达式编译器的行为。

4.3.3. SpEL compilation

Spring Framework 4.1包含一个基本的表达式编译器。表达式通常被解释为在评估过程中提供了很大的动态灵活性,但不能提供最佳性能。对于偶然的表达用法,这很好,但是当像Spring Integration这样的其他组件使用时,性能可能非常重要,并且不需要动态性。

新的SpEL编译器旨在解决此需求。编译器将在评估过程中动态生成一个真正的Java类,以体现表达式行为并使用它来实现更快的表达式评估。由于缺少对表达式的打字,编译器在执行编译时使用在表达式的解释评估期间收集的信息。例如,它并不完全知道表达式的属性引用的类型,但在第一次解释评估期间,它将找出它是什么。当然,如果各种表达式元素的类型随着时间的推移而变化,那么基于这些信息的编译可能会在稍后造成麻烦。出于这个原因,编译最适合于在重复评估时其类型信息不会改变的表达式。

对于像这样的基本表达式:
someArray[0].someProperty.someOtherProperty < 0.1

其中涉及数组访问,某些属性取消引用和数字操作,性能增益可能非常明显。 在50000次迭代的示例微基准测试中,仅使用解释器评估75ms,使用表达式的编译版本仅评估3ms。

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枚举值(off,immediate或mixed)。

Compiler limitations

在Spring Framework 4.1中,基本的编译框架已经到位。 但是,该框架还不支持编译各种表达式。 最初的重点是可能用于性能关键环境的常用表达式。 目前无法编译这些表达式:

  • 涉及分配的表达
  • 表达依赖于转换服务
  • 使用自定义解析器或访问器的表达式
  • 使用选择或投影的表达式

越来越多类型的表达将在未来进行编辑。

4.4. Expression support for defining bean definitions

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

4.4.1. XML based configuration

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

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

4.4.2. Annotation-based configuration

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

    // ...
}

4.5. Language Reference

4.5.1. Literal expressions

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

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

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()进行分析。

4.5.2. Properties, Arrays, Lists, Maps, Indexers

使用属性引用进行浏览很容易:只需使用句点来指示嵌套的属性值。 Inventor类,pupin和tesla的实例用在示例中使用的Classes一节中列出的数据填充。 为了“向下”导航并获得特斯拉的出生年份和普平的出生城市,使用了以下表达式。

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

通过指定括号内的文字键值来获得映射的内容。 在这种情况下,因为Officer地图的键是字符串,所以我们可以指定字符串文字。

// 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.5.3. Inline lists

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

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

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

{}本身意味着一个空的列表。 出于性能原因,如果列表本身完全由固定文字组成,则会创建一个常量列表来表示表达式,而不是在每个评估中创建一个新列表。

4.5.4. Inline 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);

{:}本身意味着一张空白的地图。 出于性能原因,如果地图本身由固定文字或其他嵌套常量结构(列表或地图)组成,则会创建常量地图来表示表达式,而不是在每次评估中构建新地图。 引用地图键是可选的,上面的例子没有使用带引号的键。

4.5.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.5.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.5.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);

根据一个简单的规则,对空的比较大于/小于:null在这里被视为无(即不为零)。 因此,任何其他值总是大于空(X> null始终为真),并且其他值永远不会小于空(X <null始终为false)

如果您更喜欢数字比较,请避免基于数字的空比较,以便与零比较(例如,X> 0或X <0)

除标准关系运算符外,SpEL还支持基于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);

注意原始类型,因为它们会立即装箱到包装类型,所以如预期的那样,1个instanceof T(int)的计算结果为false,而1个instanceof T(Integer)的计算结果为true。

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

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

4.5.8. Assignment

属性的设置是通过使用赋值运算符完成的。 这通常是在对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);

4.5.9. Types

特殊的T运算符可用于指定java.lang.Class(类型)的实例。 也使用此运算符调用静态方法。 StandardEvaluationContext使用TypeLocator来查找类型,并且可以通过理解java.lang包来构建StandardTypeLocator(可以替换它)。 这意味着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);

4.5.10. Constructors

可以使用new运算符调用构造函数。 除了基本类型和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);

4.5.11. Variables

变量可以使用语法#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"
The #this and #root variables

变量#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();
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);

4.5.12. Functions

您可以通过注册可在表达式字符串内调用的用户定义函数来扩展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", String.class));

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

4.5.13. Bean references

如果已经使用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.5.14. Ternary Operator (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运算符的下一节,以获取三元运算符的更简短的语法。

4.5.15. The Elvis Operator

Elvis运算符缩短了三元运算符语法,并用于Groovy语言。 使用三元运算符语法,通常必须重复两次变量,例如:

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

相反,你可以使用猫王操作符,它的名字与猫王的发型相似。

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

4.5.16. Safe Navigation operator

安全导航运算符用于避免NullPointerException,并且来自Groovy语言。 通常,当您访问对象时,可能需要在访问对象的方法或属性之前验证它是否为空。 为了避免这种情况,安全导航运算符将简单地返回null而不是抛出异常。

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}”)
如果已定义,则会注入系统属性pop3.port;如果未定义,则会注入25。

4.5.17. Collection Selection

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

选择使用语法。[selectionExpression]。 这将过滤集合并返回包含原始元素子集的新集合。 例如,选择可以让我们轻松获得塞尔维亚发明者名单:

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

在列表和地图上都可以进行选择。 在前一种情况下,针对每个单独列表元素评估选择标准,同时针对映射针对每个映射条目(Java类型Map.Entry的对象)评估选择标准。 地图条目将其键和值作为用于选择的属性进行访问。

这个表达式将返回一个新的地图,该地图由入口值小于27的原始地图的那些元素组成。

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

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

4.5.18. Collection Projection

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

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

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

4.5.19. Expression templating

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

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

// evaluates to "random number is 0.7038186818312008"

字符串通过将文本文本’random number is’与评估表达式的结果进行评估,在这种情况下是调用random()方法的结果。 方法parseExpression()的第二个参数的类型是ParserContext。 ParserContext接口用于影响表达式如何解析以支持表达式模板功能。 TemplateParserContext的定义如下所示。

public class TemplateParserContext implements ParserContext {

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

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

    public boolean isTemplate() {
        return true;
    }
}

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

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值