【深入浅出Spring原理及实战】「夯实基础系列」360全方位渗透和探究SpringEL的开发实战指南(序章)

360全方位渗透和探究SpringEL的开发实战指南

  • Spring表达式语言(SpEL)
    • 概念介绍
    • 技术无关性
    • 功能独立性
    • 功能概览
    • Spring表达式接口进行表达式评估
      • 代码案例分析
        • concat方法
        • bytes属性
      • 属性访问操作
        • 代码案例
      • 字符串构造器操作
      • 数据获取类型信息
      • 案例介绍
    • 总体分析

Spring表达式语言(SpEL)

本文将介绍 SpEL 的功能、API 和语言语法。

概念介绍

Spring 表达式语言(SpEL)是一种功能强大的表达式语言,用于在运行时查询和操作对象图。它的语法与 Unified EL 相似,但提供了更多功能,其中最主要的是方法调用和基本的字符串模板功能。

技术无关性

虽然还有其他几种 Java 表达式语言,如 OGNL、MVEL 和 JBoss EL 等,但创建 SpEL 的目的是为 Spring 社区提供一种支持良好的表达式语言,可用于 Spring 产品组合中的所有产品。SpEL 的语言特性是由 Spring 产品组合中的项目需求推动的,其中包括在基于 Eclipse 的 SpringSource 工具套件中支持代码补全的需求。尽管如此,SpEL 基于与技术无关的 API,允许在需要时集成其他表达式语言实现。

功能独立性

虽然 SpEL 是 Spring 产品组合中表达式评估的基础,但它与 Spring 没有直接联系,可以独立使用。为了实现自包含,本章中的许多示例将 SpEL 视为独立的表达式语言来使用。这种方式需要创建一些基础架构类,例如解析器。大多数 Spring 用户不需要直接处理这些基础结构,只需编写表达式字符串进行评估。

功能概览

表达式语言支持以下功能:
【深入浅出Spring原理及实战】「夯实基础系列」360全方位渗透和探究SpringEL的开发实战指南(序章)_第1张图片

Spring表达式接口进行表达式评估

以下代码介绍了如何使用 SpEL API 来评估字面字符串表达式 “Hello World”:

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class SpELDemo {
    public static void main(String[] args) {
        // 创建表达式解析器
        ExpressionParser parser = new SpelExpressionParser();
        // 定义要评估的表达式
        Expression expression = parser.parseExpression("\"Hello World\"");
        // 通过表达式评估获取结果
        String result = expression.getValue(String.class);
        // 打印结果
        System.out.println(result);
    }
}

在上述代码中,我们首先创建了一个 SpEL 表达式解析器 SpelExpressionParser。然后,我们定义了要评估的表达式 "Hello World",并使用表达式解析器对其进行解析。接下来,通过调用 getValue() 方法来评估表达式并获取结果。最后,我们将结果打印出来。

代码案例分析

使用以下 SpEL 类和接口来处理消息变量,其位于 org.springframework.expression 及其子包和 spel.support 包中。

  • ExpressionParser 接口负责解析表达式字符串。在本例中,表达式字符串是一个字符串字面量,由周围的单引号表示。
  • Expression 接口负责评估先前定义的表达式字符串。在调用 parser.parseExpression()exp.getValue() 时可能会出现两种异常:ParseExceptionEvaluationException

SpEL 还支持其他功能,如调用方法、访问属性和调用构造函数。

举个例子,您可以在字符串字面量上调用 “concat” 方法来执行字符串拼接操作。

concat方法

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

bytes属性

当前的 message 值是 “Hello World!”。您可以通过以下方式调用 JavaBean 属性,例如字符串属性 “Bytes”:

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

以上代码将返回字符串属性 “Bytes” 的值。

属性访问操作

SpEL 还支持使用标准的 “点” 符号来访问嵌套属性,例如 prop1.prop2.prop3,以及设置属性值。此外,SpEL 还可以访问公共字段。
通过使用 SpEL,您可以轻松地访问嵌套属性的值:

prop1.prop2.prop3

以上代码将返回 prop1 属性中的 prop2 属性中的 prop3 属性的值。

同时,您也可以使用 SpEL 设置嵌套属性的值:

prop1.prop2.prop3 = newValue

以上代码将设置 prop1 属性中的 prop2 属性中的 prop3 属性的值为 newValue。

此外,SpEL 还可以访问公共字段:

fieldName

以上代码将返回公共字段 fieldName 的值。

代码案例

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 getValue(Class 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");
EvaluationContext context = new StandardEvaluationContext(tesla);
String name = (String) exp.getValue(context);

上述代码中,我们将 inventor 对象作为根对象传递给表达式的 getValue 方法,并指定要获取的属性名称为 "name"。返回的值将会被自动转换为指定的结果类型。

将字符串变量"name"的值设置为"Nikola Tesla"。 在StandardEvaluationContext类中,您可以指定要针对哪个对象评估"name"属性。 如果根对象很可能不会改变,您可以使用这种机制,只需在评估上下文中设置一次即可。 如果根对象可能会反复更改,则可以在每次调用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);    

在这种情况下,发明家特斯拉直接提供给getValue方法。表达式评估基础架构会内部创建并管理一个默认的评估上下文,无需提供上下文实例。

使用标准评估上下文(StandardEvaluationContext)的构建成本相对较高,并且在重复使用过程中会积累缓存状态,从而使后续的表达式评估能够更快地执行。因此,最好在可能的情况下缓存并重复使用它们,而不是为每次表达式求值都构建新的上下文实例。

在某些情况下,我们希望在每次调用getValue时提供不同的根对象,但仍然使用配置好的评估上下文。在这种情况下,传递给调用的根对象将覆盖评估上下文中指定的任何根对象(如果有)。

案例介绍

作为最后一个介绍性示例,我们将使用之前的示例中的Inventor对象来演示如何使用布尔运算符。

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

总体分析

在独立使用SpEL时,通常需要创建解析器、解析表达式,并且可能还需要提供评估上下文和根上下文对象。不过,更常见的做法是将SpEL表达式字符串作为配置文件的一部分,例如Spring Bean或Spring Web Flow的定义。在这种情况下,解析器、评估上下文、根对象以及任何预定义变量都会被隐式设置,用户只需要指定表达式即可。

你可能感兴趣的:(#,深入浅出Java原理及实战,spring,java,后端)