[译] SpEL:Spring 表达式语言

即将推出更多独家限量资料

获取干货请 点击左上角蓝色“ 逸飞兮关注我

功能列表

SpEL支持一下功能:

  • 字面量表达式

  • 布尔运算符和相关操作

  • 常用表达式

  • 类表达式

  • 访问属性、数组、列表和映射

  • 方法调用

  • 关系运算符

  • 分配、定义

  • 调用构造方法

  • 引用Bean

  • 构造数组

  • 内联list

  • 内联映射

  • 三元运算符

  • 变量

  • 自定义功能

  • 集合推断

  • 集合选择

  • 模板表达式

计算

//计算字符串表达式
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
// message 变量的值为 'Hello World'
String message = (String) exp.getValue();

最可能用的SpEL类和接口在 org.springframework.expression 包中,例如: spel.support 。  ExpressionParser 接口负责解析字符串表达式。在上面的示例中,字符串表达式是由周围的单引号表示的字符串字面量。 Expression 接口负责计算先前定义的字符串表达式。当调用  parser.parseExpression 和  exp.getValue 时,会抛出  ParseException 和  EvaluationException 异常。

调用方法

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'.concat('!')");
// message 变量的值为 'Hello World!'
String message = (String) exp.getValue();

调用属性

ExpressionParser parser = new SpelExpressionParser();
// 调用 getBytes()
Expression exp = parser.parseExpression("'Hello World'.bytes");
// message 变量的值为 'Hello World!'
byte[] bytes = (byte []) exp.getValue();

SpEL 还支持使用标准的点符号(如:prop1.prop2.prop3)以及相应的属性值设置来访问嵌套属性。也可以访问public属性。

ExpressionParser parser = new SpelExpressionParser();
// 调用 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(ClassT> desiredResultType) 。使用此方法则无需将表达式的值强转成所需的结果类型。如果该值不能强转或使用已定义的类型转化器而转成  T 则抛出  EvaluationException 。SpEL的常见用法是提供一个字符串表达式,该字符串针对特定的对象实例(根对象)来进行计算。下例演示如何从  Inventor 类实例中检索  name 属性 或创建布尔条件:

// Create and set a calendar
GregorianCalendar c = new GregorianCalendar();
c.set(1856, 7, 9);

// The constructor arguments are name, birthday, and nationality.
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();

Expression exp = parser.parseExpression("name"); // Parse name as an expression
String name = (String) exp.getValue(tesla);
// name == "Nikola Tesla"

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

EvaluationContext

当计算表达式以解析属性、方法、字段并类型转换时,使用 EvaluationContext 接口,Spring 提供了两种实现:

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

  • StandardEvaluationContext :公开SpEL语言功能和配置选项的全部集合。可以使用它来指定默认的根对象并配置每个可用的计算相关策略。

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

  • 仅支持自定义的 PropertyAccessor (无反射)

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

  • 绑定属性到可读写的数据

类型转换

默认情况下,SpEL使用Spring coreorg.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);

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

// "false" is passed in here as a String. SpEL and the conversion service
// will recognize that it needs to be a Boolean and convert it accordingly.
parser.parseExpression("booleanList[0]").setValue(context, simple, "false");

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

解析器配置

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

Spring Framework 4.1包含一个基本的表达式编译器。通常对表达式进行解释,这在计算过程中提供了很大的动态灵活性,但没有提供最佳性能。对于偶尔使用表达式,这很好,但是当与其他组件(如Spring Integration)一起使用时,性能可能非常重要,并且不需要动态性。SpEL编译器旨在满足这一需求。在计算过程中,编译器生成一个Java类,该类体现了运行时的表达式行为,并使用该类来实现更快的表达式评估。由于缺少在表达式周围输入内容的信息,因此编译器在执行编译时会使用在表达式的解释求值过程中收集的信息。例如,它不仅仅从表达式中就知道属性引用的类型,而是在第一次解释求值时就知道它是什么。当然,如果各种表达元素的类型随时间变化,则基于此类派生信息进行编译会在以后引起麻烦。因此,最适合编译在重复求值时信息不会改变的表达式。

someArray[0].someProperty.someOtherProperty < 0.1

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

编译器配置

默认情况下不打开编译器,但是可以通过两种不同的方式之一来打开它。当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 枚举值( offimmediate ,或 mixed )。

编译器限制

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

  • 涉及赋值的表达式

  • 依赖转换服务的表达式

  • 使用自定义 resolvers 或 accessors 的表达式

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

Bean定义中的表达式

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

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>

注解配置

要指定默认值,可以将 @Value 注解放在字段,方法以及方法或构造器参数上。

设置属性默认值

public class FieldValueTestBean {

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

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

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

以下示例等效的,但使用set方式

set注入默认值

public class PropertyValueTestBean {

    private String defaultLocale;

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

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

自动装配的方法

public class SimpleMovieLister {

    private MovieFinder movieFinder;
    private String defaultLocale;

    @Autowired
    public void configure(MovieFinder movieFinder,
            @Value("#{ systemProperties['user.region'] }") String defaultLocale) {
        this.movieFinder = movieFinder;
        this.defaultLocale = defaultLocale;
    }

    // ...
}

构造器

public class MovieRecommender {

    private String defaultLocale;

    private CustomerPreferenceDao customerPreferenceDao;

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

    // ...
}

对象类型

字面量表达式

支持的字面量表达式的类型为字符串,数值(int,实数,十六进制),布尔值和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() 解析实数。

属性、数组、列表、映射、索引

所使用的的类见最后一小节 使用属性引用进行导航很容易。为此,使用点号来指示嵌套的属性值。  Inventor 类  pupin 和  tesla 的实例被示例使用的类中列出的数据填充。要向下导航并获取Tesla的出生年份和Pupin的出生城市,使用以下表达式:

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

// Inventions Array

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

// Members List

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

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

通过在方括号内指定文字键值可以获取映射的内容。在下例中,由于 Officers 映射的键是字符串,因此我们可以指定字符串字面量:

// Officer's Dictionary

Inventor pupin = parser.parseExpression("Officers['president']").getValue(
        societyContext, Inventor.class);

// evaluates to "Idvor"
String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(
        societyContext, String.class);

// setting values
parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(
        societyContext, "Croatia");

内嵌列表

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

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

{} 本身意味着一个空列表。出于性能原因,如果列表本身完全由固定字面量组成,则会创建一个常量列表来表示该表达式(而不是在每次求值时都构建一个新列表)。

内联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本身由固定的字面量或其他嵌套的常量结构(list或map)组成,则会创建一个常量map来表示表达式(而不是在每次求值时都构建一个新的map)。映射键的引用是可选的。上面的示例 使用 带引号 的键。

构造数组

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

构造多维数组时,当前无法提供初始化程序。

方法

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

操作符

关系运算符

使用标准运算符表示法支持关系运算符(等于,不等于,小于,小于或等于,大于和大于或等于)。如下:

// 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 进行的大小比较遵循一个简单的规则: null 被视为无(不是零)。结果,任何其他值始终大于  nullX > null 始终为  true ),并且其他任何值都不小于零( X < null 始终为  false )。如果更喜欢使用数字比较,避免与  null 比较,建议使用零进行比较(例如 X > 0 或  X < 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);

警告: 注意基本类型,它们会立即装箱成包装类型,因此按预期将 1 instanceof T(int) 值计算为  false ,将  1 instanceof T(Integer) 值计算为  true

每个符号运算符也可以指定为纯字母等效项。这样可以避免使用的符号对于嵌入表达式的文档类型具有特殊含义的问题(例如在XML文档中)。等效的文字是:

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

  • lt ( < )

  • gt ( > )

  • le ( <= )

  • ge ( >= )

  • eq ( == )

  • ne ( != )

  • div ( / )

  • mod ( % )

  • not ( ! )

所有文本运算符都不区分大小写。

逻辑运算符

SpEL支持以下逻辑运算符:

  • and ( && )

  • or ( || )

  • not ( ! ) 示例如下:“`java // — 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);

### 数学运算符
可以在所有数字和字符串上使用加法运算符。减法,乘法和除法运算符只能对数字使用。还可以使用模数(%)和指数幂(^)运算符。强制执行标准运算符优先级。如下:
```java
// 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

赋值运算符

要设置属性,使用赋值运算符( = )。这通常在调用 setValue 中完成,但也可以在调用 getValue 中完成。如下:

Inventor inventor = new Inventor();
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();

parser.parseExpression("Name").setValue(context, inventor, "Aleksandar Seovic");

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

类型

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

构造器

可以使用 new 操作符来调用构造器。除了基本类型( intfloat 等)和String之外,都应使用完全限定的类名。如下:

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

变量

可以使用 #variableName 语法在表达式中引用变量。通过  EvaluationContext 实现类中的  setVariable 方法来设置变量。有效的变量名称必须由以下一个或多个受支持的字符组成。

  • 字母: A-Z 和 a-z

  • 数字: 0 to 9

  • 下划线: _

  • 美元符: $ “`java Inventor tesla = new Inventor("Nikola Tesla", "Serbian");

EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build(); context.setVariable("newName", "Mike Tesla");

parser.parseExpression("Name = #newName").getValue(context, tesla); System.out.println(tesla.getName()) // "Mike Tesla"

### `#this` 和 `#root` 变量
`#this` 始终声明并引用当前的计算对象(针对不合格的引用,将对其进行解析)。`#root`  始终定义并引用根上下文对象。尽管 `#this` 随着表达式的组成部分的求值可能会有所不同,但 `#root` 始终指的是根。如下:
```java
// create an array of integers
List<Integer> primes = new ArrayList<Integer>();
primes.addAll(Arrays.asList(2,3,5,7,11,13,17));

// create parser and set variable 'primes' as the array of integers
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataAccess();
context.setVariable("primes", primes);

// all prime numbers > 10 from the list (using selection ?{...})
// evaluates to [11, 13, 17]
List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression(
        "#primes.?[#this>10]").getValue(context);

功能

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

Method method = ...;

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

如下,将反转字符串的程序方法注册:

public abstract class StringUtils {

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

然后注册并使用它:

ExpressionParser parser = new SpelExpressionParser();

EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
context.setVariable("reverseString",
        StringUtils.class.getDeclaredMethod("reverseString", String.class));

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

Bean引用

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

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// 在计算期间这将最终在MyBeanResolver上调用 resolve(context,"something")
Object bean = parser.parseExpression("@something").getValue(context);

要访问工厂bean本身,应在bean名称前添加一个&符号。

ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new MyBeanResolver());

// 在计算期间这将最终在MyBeanResolver上调用 resolve(context,"&foo")
Object bean = parser.parseExpression("&foo").getValue(context);

三元运算符

可以使用三元运算符在表达式内部执行if-then-else条件逻辑。

String falseString = parser.parseExpression(
        "false ? 'trueExp' : 'falseExp'").getValue(String.class);
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 操作符

Elvis运算符是三元运算符语法的简化,并且在 Groovy 语言中使用。使用三元运算符语法,通常必须将变量重复两次,如下:

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

使用Elvis如下:

ExpressionParser parser = new SpelExpressionParser(); String name = parser.parseExpression("name?:'Unknown'").getValue(String.class); System.out.println(name); // 'Unknown'
ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
String name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Nikola Tesla

tesla.setName(null);
name = parser.parseExpression("Name?:'Elvis Presley'").getValue(context, tesla, String.class);
System.out.println(name);  // Elvis Presley

可以在表达式中使用Elvis运算符来应用默认值

// 系统属性 pop3.port 如果定义,将注入,否则将注入25。
@Value("#{systemProperties['pop3.port'] ?: 25}")

安全导航操作符

安全导航运算符用于避免 NullPointerException ,来自 Groovy 语言。通常,当引用一个对象时,可能需要在访问该对象的方法或属性之前验证其是否为null。为了避免这种情况,安全导航运算符返回null而不是引发异常。如下:

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

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

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

tesla.setPlaceOfBirth(null);
city = parser.parseExpression("PlaceOfBirth?.City").getValue(context, tesla, String.class);
System.out.println(city);  // null - does not throw NullPointerException!!!

集合选择

选择是一个强大的表达式语言特性,通过从源集合中进行选择来将其转换为另一个集合。选择使用的语法 .?[selectionExpression] 。它过滤集合并返回一个包含原始元素子集的新集合。例如,通过选择,可以轻松地获得Serbian发明者的列表,如下:

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

在List和map上都可以选择。对于列表,将针对每个单独的列表元素计算选择标准。对于map,针对每个map entry(Java类型的对象 Map.Entry )计算选择标准。每个map entry都有其键和值,可作为属性进行访问,以供选择使用。以下表达式返回一个新map,由原始map中entry value 小于 27 的那些元素组成:

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

除了返回所有选定的元素外,能只检索第一个或最后一个值。要获得与所选内容匹配的第一个条目,语法为 .^[selectionExpression] 。要获取最后的匹配选择,语法为  .$[selectionExpression]

集合投影

投影使集合可以驱动子表达式的求值,结果是一个新的集合。投影的语法为 .![projectionExpression] 。例如,假设我们有一个发明人列表,但想要他们出生的城市的列表。实际上,我们希望为发明人列表中的每个条目计算“ placeOfBirth.city”。

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

还可以使用map来驱动投影,在这种情况下,将根据map中的每个entry( Map.Entry )对投影表达式进行求值。跨map的投影结果是一个列表,其中包含针对每个map entry的投影表达式的计算。

表达式模板

表达式模板允许将字面量文本与一个或多个计算混合。每个计算都由可以定义的前缀和后缀字符定界。常见的选择是 #{ } 用作定界符

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

// 计算结果是 "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;
    }
}

示例中使用的类

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

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() {
    }

    // getter and setter ...
}
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;
    }

    // getter and setter ...
}
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;
    }
}

参考资料

Spring 4 官方文档学习(五)核心技术之SpEL

Spring官方文档

你的关注是 对我 巨大鼓励

[译] SpEL:Spring 表达式语言

我在看  ↓↓

原文 

https://mp.weixin.qq.com/s/IF0dvRKmDLqIIeATeYvnqQ

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » [译] SpEL:Spring 表达式语言

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址