SpringFramework核心技术四:Spring表达式语言知识点(SpEL)

简介: Spring表达式Sprng表达式,可以适用于几乎所有的Spring产品中,是一种非常重要的表达式语言,下面我们一起来看看。

Spring表达式

Sprng表达式,可以适用于几乎所有的Spring产品中,是一种非常重要的表达式语言,下面我们一起来看看。

一、介绍

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类被用作表达式评估的目标对象。这些类声明和用于填充它们的数据在本章最后列出。

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

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

二、Evaluation

以下代码引入了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负责评估以前定义的表达式字符串。有两个例外,可以被抛出,ParseException和EvaluationException在调用parser.parseExpression和exp.getValue 分别被抛出。

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)。使用此方法不需要将表达式的值转换为所需的结果类型。EvaluationException如果该值不能T转换为类型或使用注册类型转换器进行转换,则会抛出An 。

SpEL更常见的用法是提供一个针对特定对象实例(称为根对象)进行评估的表达式字符串。该示例显示如何name从Inventor类的实例中检索属性或创建布尔条件:

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

三、EvaluationContext

EvaluationContext评估表达式以解析属性,方法或字段并帮助执行类型转换时使用该接口。有两个开箱即用的实现。

  • SimpleEvaluationContext - 针对不需要SpEL语言语法的全部范围并且应该受到有意限制的表达式类别,公开Spal语言特性和配置选项的子集。示例包括但不限于数据绑定表达式,基于属性的过滤器等。

  • StandardEvaluationContext - 公开全套SpEL语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。

SimpleEvaluationContext旨在仅支持SpEL语言语法的一个子集。它不包括 Java类型引用,构造函数和bean引用。它还要求明确选择对表达式中属性和方法的支持级别。默认情况下,create()静态工厂方法只启用对属性的读取访问。您还可以获取构建器以配置所需的确切支持级别,并将目标作为以下一项或几项的组合:

  • PropertyAccessor仅自定义(不反射)
  • 用于只读访问的数据绑定属性
  • 用于读取和写入的数据绑定属性

类型转换

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

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

class Simple {
    public List<Boolean> booleanList = new ArrayList<Boolean>();
}

Simple simple = new Simple();
simple.booleanList.add(true);

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

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

四、解析器配置

可以使用解析器配置对象(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次迭代的示例微基准测试中,仅使用解释器评估75ms,使用表达式的编译版本仅评估3ms。

编译器配置

编译器默认情况下未打开,但有两种方法可以打开它。可以使用前面讨论过的解析器配置过程打开它,或者当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)。

编译器限制

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

  • 涉及分配的表达
  • 表达依赖于转换服务
  • 使用自定义解析器或访问器的表达式
  • 使用选择或投影的表达式
  • 越来越多类型的表达将在未来进行编辑。

六、表达式在bean定义中

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

1.XML配置

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

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

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

变量systemProperties是预定义的,所以你可以在你的表达式中使用它,如下所示。请注意,您不必# 在此上下文中将符号放在预定义变量前面。

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

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

例如,您也可以通过名称引用其他bean属性。

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

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

<bean id="shapeGuess" class="org.spring.samples.ShapeGuess">
    <property name="initialShapeSeed" value="#{ numberGuess.randomNumber }"/>

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

2.基于注解的使用(推荐的使用方式)

该@Value注释可被放置在字段,方法和方法/构造函数的参数来指定一个缺省值。
以下是设置字段变量的默认值的示例。

public static class FieldValueTestBean

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

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

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

}

下面显示了等价但属性setter方法。

public static class PropertyValueTestBean

    private String defaultLocale;

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

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

}

自动装配的方法和构造函数也可以使用@Value注释。

public class SimpleMovieLister {

    private MovieFinder movieFinder;
    private String defaultLocale;

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

    // ...
}
public class 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;
    }

    // ...
}

好啦,上面使一些Spring表达式的一些基本的知识点。下一节看看如何使用!

目录
相关文章
|
3月前
|
开发框架 负载均衡 Java
当热门技术负载均衡遇上 Spring Boot,开发者的梦想与挑战在此碰撞,你准备好了吗?
【8月更文挑战第29天】在互联网应用开发中,负载均衡至关重要,可避免单服务器过载导致性能下降或崩溃。Spring Boot 作为流行框架,提供了强大的负载均衡支持,通过合理分配请求至多台服务器,提升系统可用性与可靠性,优化资源利用。本文通过示例展示了如何在 Spring Boot 中配置负载均衡,包括添加依赖、创建负载均衡的 `RestTemplate` 实例及服务接口调用等步骤,帮助开发者构建高效、稳定的应用。随着业务扩展,掌握负载均衡技术将愈发关键。
75 6
|
1月前
|
存储 Java API
简单两步,Spring Boot 写死的定时任务也能动态设置:技术干货分享
【10月更文挑战第4天】在Spring Boot开发中,定时任务通常通过@Scheduled注解来实现,这种方式简单直接,但存在一个显著的限制:任务的执行时间或频率在编译时就已经确定,无法在运行时动态调整。然而,在实际工作中,我们往往需要根据业务需求或外部条件的变化来动态调整定时任务的执行计划。本文将分享一个简单两步的解决方案,让你的Spring Boot应用中的定时任务也能动态设置,从而满足更灵活的业务需求。
82 4
|
2月前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
38 1
|
2月前
|
前端开发 安全 Java
技术进阶:使用Spring MVC构建适应未来的响应式Web应用
【9月更文挑战第2天】随着移动设备的普及,响应式设计至关重要。Spring MVC作为强大的Java Web框架,助力开发者创建适应多屏的应用。本文推荐使用Thymeleaf整合视图,通过简洁的HTML代码提高前端灵活性;采用`@ResponseBody`与`Callable`实现异步处理,优化应用响应速度;运用`@ControllerAdvice`统一异常管理,保持代码整洁;借助Jackson简化JSON处理;利用Spring Security增强安全性;并强调测试的重要性。遵循这些实践,将大幅提升开发效率和应用质量。
63 7
|
3月前
|
缓存 NoSQL Java
SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)
Spring Cache 是 Spring 提供的简易缓存方案,支持本地与 Redis 缓存。通过添加 `spring-boot-starter-data-redis` 和 `spring-boot-starter-cache` 依赖,并使用 `@EnableCaching` 开启缓存功能。JetCache 由阿里开源,功能更丰富,支持多级缓存和异步 API,通过引入 `jetcache-starter-redis` 依赖并配置 YAML 文件启用。Layering Cache 则提供分层缓存机制,需引入 `layering-cache-starter` 依赖并使用特定注解实现缓存逻辑。
952 1
SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)
|
2月前
|
JavaScript 前端开发 Java
【颠覆传统】Spring框架如何用WebSocket技术重塑实时通信格局?揭秘背后的故事与技术细节!
【9月更文挑战第4天】随着Web应用对实时交互需求的增长,传统的HTTP模型已无法满足现代应用的要求,特别是在需要持续、双向通信的场景下。WebSocket协议由此诞生,提供全双工通信渠道,使服务器与客户端能实时互发消息。作为Java开发中最受欢迎的框架之一,Spring通过其WebSocket模块支持这一协议,简化了WebSocket在Spring应用中的集成。
55 0
|
3月前
|
前端开发 Java Spring
Spring与Angular/React/Vue:当后端大佬遇上前端三杰,会擦出怎样的火花?一场技术的盛宴,你准备好了吗?
【8月更文挑战第31天】Spring框架与Angular、React、Vue等前端框架的集成是现代Web应用开发的核心。通过RESTful API、WebSocket及GraphQL等方式,Spring能与前端框架高效互动,提供快速且功能丰富的应用。RESTful API简单有效,适用于基本数据交互;WebSocket支持实时通信,适合聊天应用和数据监控;GraphQL则提供更精确的数据查询能力。开发者可根据需求选择合适的集成方式,提升用户体验和应用功能。
92 0
|
3月前
|
Java 数据库连接 数据库
告别繁琐 SQL!Hibernate 入门指南带你轻松玩转 ORM,解锁高效数据库操作新姿势
【8月更文挑战第31天】Hibernate 是一款流行的 Java 持久层框架,简化了对象关系映射(ORM)过程,使开发者能以面向对象的方式进行数据持久化操作而无需直接编写 SQL 语句。本文提供 Hibernate 入门指南,介绍核心概念及示例代码,涵盖依赖引入、配置文件设置、实体类定义、工具类构建及基本 CRUD 操作。通过学习,你将掌握使用 Hibernate 简化数据持久化的技巧,为实际项目应用打下基础。
178 0
|
3月前
|
Java 前端开发 Spring
技术融合新潮流!Vaadin携手Spring Boot、React、Angular,引领Web开发变革,你准备好了吗?
【8月更文挑战第31天】本文探讨了Vaadin与Spring Boot、React及Angular等主流技术栈的最佳融合实践。Vaadin作为现代Java Web框架,与其他技术栈结合能更好地满足复杂应用需求。文中通过示例代码展示了如何在Spring Boot项目中集成Vaadin,以及如何在Vaadin项目中使用React和Angular组件,充分发挥各技术栈的优势,提升开发效率和用户体验。开发者可根据具体需求选择合适的技术组合。
72 0
|
3月前
|
缓存 Java 数据库连接
Spring Boot 资源文件属性配置,紧跟技术热点,为你的应用注入灵动活力!
【8月更文挑战第29天】在Spring Boot开发中,资源文件属性配置至关重要,它让开发者能灵活定制应用行为而不改动代码,极大提升了可维护性和扩展性。Spring Boot支持多种配置文件类型,如`application.properties`和`application.yml`,分别位于项目的resources目录下。`.properties`文件采用键值对形式,而`yml`文件则具有更清晰的层次结构,适合复杂配置。此外,Spring Boot还支持占位符引用和其他外部来源的属性值,便于不同环境下覆盖默认配置。通过合理配置,应用能快速适应各种环境与需求变化。
43 0