java 的第一个分享,一个富有表现力的表达式引擎的 Java 实现
一个富有语言表现力的表达式引擎 Java 实现。
先附上项目地址:github.com/slince/expression
表达式引擎在 java 里有很多个知名实现,比如 spring 的 spel
和 apache 的 jexl
,都是非常优秀的作品;那么为什么要选择实现表达式引擎呢;主要因为这么几个原因;
对编译原理的兴趣
作者是 phper 出身, php 里有两个非常知名的作品,模板引擎 twig 和 symfony 的 expression language,都是出自大神 fabpot,因为好奇的原因研究过实现自此就感受到了编译的乐趣,后面自己看了一些抽象语法树,词法分析器,语法解析器的文章和案例。随着深入,便渐渐萌生了也写一个的念头;于是 typescript 版的 expression-language.js 和 java 版的本项目就诞生了;
现有表达式引擎的不足
java 代码中对对象的判空是非常常见的,但在表达式里判断并进行逻辑转换就比较麻烦,比如如下表达式
Evaluator.evaluate("user.tags.size()")
上述表达式意图得到用户的标签的数量,在对象均不为 null
的情况下是没有问题的,一旦 tags
为 null
,运行便会报空指针异常;
如果是 java 代码我们可以判空:如果为空则强行返回0;
为了在表达式里实现此效果,本实现里借用了 twig 的 filter 语法
Evaluator.evaluate("user.tags|size")
过滤器 size
会判断是否是 null
; 表达式内置了一些过滤器 github.com/slince/expression/blob/...
当然你也可以扩展表达式引擎提供自己的过滤器;
JSONPATH的灵感
如果大家用过 json path 的话会惊讶于 json path 取子代元素的的灵巧,jsonpath 只能处理 json , 在对象上是不适用的,因此在本实现里,借鉴了部分 json path 的功能
搜索子代同名属性
Evaluator.evaluate("user..name")
上述表达式会将
user
对象的所有属性以及属性对象的属性中名称为name
的属性值遍历出来平铺遍历
list
对象集合的指定属性
Evaluator.evaluate("user.books|fluent.author.name.collect()")
上述表达式可以把某用户的所有书籍的作者提取出来;fluent
过滤器会把 List<Book>
转换成类 Fluency<Book>
,以方面平滑的获取每一个 Book
的作者的名称;
执行过程类似 java 代码:
user.books.stream().filter(v -> v.getAuthor().getName()).collect(Collector.toList());
安装
<dependency>
<groupId>io.github.slince</groupId>
<artifactId>expression</artifactId>
<version>0.0.2-RELEASE</version>
</dependency>
ObjectPath:
<dependency>
<groupId>io.github.slince</groupId>
<artifactId>expression-data-path</artifactId>
<version>0.0.2-RELEASE</version>
</dependency>
ObjectPath 使用文档见这里;
快速开始
import io.github.slince.expression.Evaluator;
import io.github.slince.expression.MapContext;
import java.util.Arrays;
import java.util.List;
public class Book {
public static void main(String[] args) {
// 创建一本书实例
Book book = new Book("The Lady of the Camellias", 12.89f, Arrays.asList("Love Story", "France", null));
MapContext ctx = new MapContext();
ctx.setVar("book", book);
// 执行表达式
// "The Lady of the Camellias"
System.out.println(Evaluator.INSTANCE.evaluate("book.name", ctx));
// 3
System.out.println(Evaluator.INSTANCE.evaluate("book.tags|size", ctx));
// 22.89f
System.out.println(Evaluator.INSTANCE.evaluate("book.price + 10", ctx));
}
private final String name;
private final float price;
private final List<String> tags;
public Book(String name, float price, List<String> tags) {
this.name = name;
this.price = price;
this.tags = tags;
}
public String getName() {
return name;
}
public float getPrice() {
return price;
}
public List<String> getTags() {
return tags;
}
}
文档
更多文档请查看详细文档
问题反馈
报告 Issue: github issues
LICENSE
The Apache 2.0 license. See Apache-2.0