在进行表单或者流程引擎设计时,我们常常需要构建各种各样的表达式或者规则,以此来驱动业务流程的顺利运转。这些表达式和规则就如同精密的齿轮,相互协作,让业务逻辑得以有序执行。今天,我们就来全面盘点一下 Java 开发中常用的那些表达式引擎。
这些表达式引擎在 Java 开发领域应用广泛,相信很多开发者都对它们有所了解。接下来,让我们一起重新深入认识一下它们。
Spring EL
官方资源
- 官方文档:
https://docs.spring.io/spring-framework/reference/core/expressions.html
- 官方示例:
https://github.com/spring-projects/spring-framework/tree/master/spring-expression
Spring Expression Language(SpEL)是 Spring 框架中一项强大的功能,它为我们在运行时查询和操作对象图提供了便捷且高效的方式。以下是 SpEL 的几个核心特性:
- 动态数据处理能力:SpEL 允许我们在运行时执行复杂的数据查询和操作。无论是读取 bean 的属性值、调用方法,还是进行算术运算、逻辑判断,SpEL 都能轻松应对。这使得我们的应用程序能够根据不同的运行时条件灵活地处理数据。
- 与 Spring 框架深度集成:SpEL 广泛应用于 Spring 的各个模块中。在 Spring Security 里,它用于定义访问控制表达式,帮助我们实现细粒度的权限管理;在 Spring Data 中,可用于查询条件的定义,简化数据查询操作;在 Spring Integration 里,还能实现消息路由的功能,确保消息准确地传递到目标位置。
- 独特的语法结构:SpEL 表达式通常被包裹在
#{...}
之中。例如,#{property}
可以用来获取一个 bean 的属性值。它支持丰富的运算符,包括字符串、布尔、算术、关系、逻辑运算符,同时还支持方法调用、数组和列表索引访问等操作。这种简洁而强大的语法结构,使得我们可以用简洁的代码实现复杂的逻辑。 - 上下文感知特性:SpEL 能够敏锐地感知 Spring 应用上下文里的 Bean。这意味着我们可以直接在表达式中引用配置好的 bean,从而实现高度灵活的配置和运行时行为调整。例如,我们可以在表达式中引用一个服务 bean,调用其方法来完成特定的业务逻辑。
- 智能类型转换服务:SpEL 提供了内置的类型转换服务,它可以自动或者根据我们的显式要求,将一种类型的值转换为另一种类型。这在处理不同类型的数据时非常方便,避免了我们手动进行类型转换的繁琐操作。
- 安全防护机制:在使用 SpEL 时,安全性是我们必须要考虑的因素。为了避免注入攻击,Spring 提供了 ExpressionParser 的配置选项,我们可以通过它来限制表达式的执行能力,比如禁用方法调用或者属性访问等。这样可以有效地防止恶意用户通过构造恶意表达式来攻击我们的应用程序。
示例代码
SpEL 工具类
OGNL
官方资源
- 官方文档:
https://ognl.orphan.software/language-guide
- 官方示例:
https://github.com/orphan-oss/ognl
OGNL(Object - Graph Navigation Language)是一种强大的表达式语言,专门用于获取和设置 Java 对象的属性。它在许多 Java 框架中都扮演着重要的角色,尤其是在 Apache Struts2 框架中,被广泛应用于数据绑定和操作对象图。
关键特性
- 简洁的表达式语法:OGNL 允许我们以极其简单的字符串形式编写表达式来访问对象属性。例如,
person.name
就可以轻松获取person
对象的name
属性。这种简洁的语法使得代码的编写和阅读都变得非常容易。 - 强大的链式导航功能:它支持链式调用,让我们可以深入对象图进行操作。比如,
customer.address.street
会依次导航到customer
的address
属性,再从address
获取street
属性。通过链式导航,我们可以方便地访问对象的深层属性。 - 灵活的集合操作能力:OGNL 能够直接在表达式中处理集合和数组,包括遍历、筛选、投影等操作。例如,
customers.{name}
可以获取所有customers
集合中每个元素的name
属性。这使得我们在处理集合数据时更加高效。 - 上下文敏感特性:在解析 OGNL 表达式时,会充分考虑一个上下文环境,这个环境包含了变量、对象以及其他表达式可能需要的信息。通过上下文环境,我们可以在表达式中引用其他变量和对象,实现更加灵活的逻辑处理。
- 丰富的方法与构造器支持:除了属性访问,OGNL 还支持调用对象的方法和构造新对象。比如,
@myUtil.trim(name)
可以调用工具类方法,new java.util.Date()
则可以创建新对象。这为我们在表达式中实现复杂的逻辑提供了更多的可能性。 - 全面的逻辑运算支持:它支持
if
、else
逻辑,以及&&
、||
等逻辑运算符,使得表达式能够处理更为复杂的逻辑判断。例如,我们可以使用if
语句来根据不同的条件执行不同的操作。 - 便捷的变量赋值功能:OGNL 不仅能够读取数据,还能设置对象属性的值。例如,
person.name = "Alice"
就可以为person
对象的name
属性赋值。这使得我们可以在表达式中直接修改对象的属性。 - 安全风险防范意识:和 SpEL 一样,在使用 OGNL 时,我们也需要格外注意表达式注入的安全风险。要确保用户输入不会被直接用于构造表达式,从而防止恶意操作。例如,我们可以对用户输入进行严格的验证和过滤。
OGNL 工具类
Aviator
官方资源
- 官方文档:
http://fnil.net/aviator/
- 官方示例:
https://github.com/killme2008/aviatorscript
Aviator 是一款轻量级的 Java 表达式执行引擎,专门为高性能的动态计算场景而设计。它特别适用于那些需要在运行时解析和执行复杂表达式的应用场景。
核心特点
- 卓越的性能表现:Aviator 对表达式的编译和执行过程进行了深度优化,特别适合对性能有严格要求的系统,如金融风控、实时计算等领域。在这些领域中,系统需要快速地处理大量的表达式,Aviator 的高性能可以满足这些需求。
- 轻松的集成体验:它提供了简单易用的 API 接口,让我们在 Java 项目中嵌入 Aviator 变得轻而易举。只需引入依赖,就可以开始编写和执行表达式。这大大降低了我们使用 Aviator 的门槛。
- 丰富的表达式支持:Aviator 支持几乎所有常见的运算需求,包括数学运算、逻辑运算、比较运算、位运算、字符串操作、三元运算、变量定义与引用、函数调用等。这种丰富的表达式支持使得我们可以用 Aviator 实现各种复杂的业务逻辑。
- 安全的沙箱机制:Aviator 提供了沙箱机制,我们可以通过它来限制表达式的执行权限,比如禁止访问某些方法或字段,从而大大提高应用的安全性。在处理用户输入的表达式时,沙箱机制可以有效地防止恶意代码的执行。
- 动态脚本执行能力:它允许在运行时动态加载和执行脚本,这一特性使得它非常适合用于规则引擎、配置驱动的系统逻辑等场景。我们可以根据不同的业务需求,动态地加载和执行不同的脚本。
- JIT 编译加速技术:Aviator 采用即时编译技术,将表达式编译成 Java 字节码执行,进一步提升了执行效率。通过 JIT 编译,表达式的执行速度可以得到显著提高。
- 便捷的数据绑定功能:我们可以方便地将 Java 对象、Map、List 等数据结构绑定到表达式上下文中,实现表达式与 Java 数据的无缝对接。这使得我们在表达式中可以直接使用 Java 数据,提高了开发效率。
- 强大的扩展能力:Aviator 支持自定义函数,用户可以根据自己的需求扩展其功能,增加特定业务逻辑的处理能力。例如,我们可以自定义一个函数来实现特定的业务算法。
Aviator 工具类
Mvel2
官方资源
- 官方文档:
https://juejin.cn/post/mvel.documentnode.com/
- 官方示例:
https://github.com/mvel/mvel
MVEL2(MVFLEX Expression Language 2)是一个强大且灵活的 Java 库,用于解析和执行表达式语言。它是 MVEL 项目的第二代版本,旨在提供高效、简洁的方式来操作对象和执行逻辑。
关键特性与使用指南
- 动态类型与静态类型混合支持:MVEL 支持动态类型,同时也允许静态类型检查。这意味着我们可以根据实际需求选择是否在编译时检查类型错误,增加了代码的灵活性和安全性。例如,在一些快速开发的场景中,我们可以使用动态类型来提高开发效率;而在对类型安全要求较高的场景中,我们可以使用静态类型检查来避免类型错误。
- 简洁的语法结构:MVEL 语法基于 Java 但更加简洁,便于编写和阅读。它适用于快速构建表达式和小型脚本。例如,我们可以用更简洁的代码来实现相同的逻辑,减少代码的冗余。
- 便捷的属性访问与方法调用:类似于其他表达式语言,MVEL 允许直接访问对象属性和调用其方法。例如,
person.name
可以访问person
对象的name
属性,list.size()
可以调用list
对象的size()
方法。这使得我们在操作对象时更加方便。 - 丰富的控制流语句支持:MVEL 支持
if
、else
、switch
、循环(for
、while
)等控制流结构,使得在表达式中实现复杂逻辑成为可能。我们可以根据不同的条件执行不同的操作,或者对集合进行遍历操作。 - 强大的模板引擎功能:MVEL2 提供了一个强大的模板引擎,可以用来生成文本输出。它类似于 Velocity 或 Freemarker,但与 MVEL 表达式无缝集成。我们可以使用模板引擎来生成动态的文本内容,如邮件模板、报表等。
- 灵活的变量赋值与函数定义:MVEL 允许直接在表达式中定义变量和函数,支持局部变量和闭包(匿名函数)。同时,它还能自动或手动进行类型转换,简化了不同数据类型间的操作。例如,我们可以在表达式中定义一个局部变量,并在后续的代码中使用它。
- 良好的集成与扩展能力:MVEL 设计为易于集成到现有 Java 项目中,同时提供了扩展点,允许用户定义自定义函数和操作符。这使得我们可以根据项目的需求对 MVEL 进行扩展,增加其功能。
- 高效的性能优化:MVEL 关注执行效率,通过优化的编译器和执行引擎来减少运行时开销。在处理大量的表达式时,MVEL 的高性能可以保证系统的响应速度。
Hutool 表达式引擎门面
官方文档
https://doc.hutool.cn/pages/ExpressionUtil/#%E4%BB%8B%E7%BB%8D
Hutool 工具包在 5.5.0 版本之后,将表达式计算引擎封装为门面模式,提供统一的 API,去除了不同表达式引擎之间的差异。目前,它支持以下几种表达式引擎:
- Aviator
- Apache Jexl3
- MVEL
- JfireEL
- Rhino
- Spring Expression Language (SpEL)
如果上述的表达式引擎不能满足我们的需求,Hutool 还支持通过 SPI 进行自定义扩展。这使得我们可以根据具体的业务需求,灵活地选择和扩展表达式引擎。
基于 Hutool 封装的工具类
总结
本文详细介绍了市面上比较常用的几种表达式引擎组件。这些引擎各有特点,适用于不同的应用场景。而 Hutool 提供的表达式门面模式,为我们使用这些表达式引擎提供了统一的接口,大大简化了开发过程。
Hutool 在工具类方面确实表现出色,几乎涵盖了我们日常开发中所需的大部分工具。
最后,文末的 demo 链接还提供了与 Spring 整合的表达式引擎聚合实现,感兴趣的读者可以进一步查看。
希望通过本文的介绍,大家能够对这些 Java 表达式引擎有更深入的了解,在实际开发中能够根据具体需求选择合适的表达式引擎,提高开发效率和代码质量。