前言
我們在設計一些表單或者流程引擎時,可能我們會設計各種各樣的表示式或者規則,我們透過各種表示式或者規則來實現我們的業務流轉。今天就來盤點一下我們經常會使用到的表示式引擎
常用表示式引擎
1、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框架中的一個強大的表示式語言,用於在執行時查詢和操作物件圖。以下是關於Spring EL的幾個關鍵點:
動態查詢和操作: SpEL允許你在執行時執行復雜的查詢和運算元據,比如讀取bean的屬性值、呼叫方法、進行算術運算、邏輯判斷等。
整合於Spring框架: SpEL廣泛應用於Spring的各種模組中,如Spring Security的訪問控制表示式、Spring Data的查詢條件定義、Spring Integration的訊息路由等。
基本語法: SpEL表示式通常被包含在#{...}中,例如#{property}用來獲取一個bean的屬性值。它支援字串、布林、算術、關係、邏輯運算子,以及方法呼叫、陣列和列表索引訪問等。
上下文感知: SpEL能夠訪問Spring應用上下文中的Bean,這意味著你可以直接在表示式中引用配置的bean,實現高度靈活的配置和執行時行為調整。
型別轉換: SpEL提供了內建的型別轉換服務,可以自動或顯式地將一種型別的值轉換為另一種型別。
安全考量: 使用SpEL時需要注意安全性,避免注入攻擊。Spring提供了ExpressionParser的配置來限制表示式的執行能力,如禁用方法呼叫或屬性訪問等。
例子:
- 訪問Bean屬性: #{myBean.propertyName}
- 方法呼叫: #{myBean.myMethod(args)}
- 三元運算子: #{condition ? trueValue : falseValue}
列表和陣列訪問: #{myList[0]}
- 算術運算: #{2+3}
spel工具類
public class SpringExpressionUtil {
private static final SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
private SpringExpressionUtil(){}
/**
* Evaluates the given Spring EL expression against the provided root object.
*
* @param rootObject The object to use as the root of the expression evaluation.
* @param expressionString The Spring EL expression to evaluate.
* @param returnType The expected return type.
* @return The result of the expression evaluation.
*/
public static <T> T evaluateExpression(Map<String, Object> rootObject, String expressionString, Class<T> returnType) {
StandardEvaluationContext context = new StandardEvaluationContext(rootObject);
rootObject.forEach(context::setVariable);
return EXPRESSION_PARSER.parseExpression(expressionString).getValue(context,returnType);
}
public static void main(String[] args) {
Map<String,Object> map = new HashMap<>();
map.put("name","lybgeek");
map.put("hello","world");
System.out.println(evaluateExpression(map,"#root.get('name')",String.class));
}
}
2、ognl
官方文件
https://ognl.orphan.software/language-guide
官方示例
https://github.com/orphan-oss/ognl
OGNL (Object-Graph Navigation Language) 是一個強大的表示式語言,用於獲取和設定Java物件的屬性。它在許多Java框架中被用作資料繫結和操作物件圖的工具,最著名的應用是在Apache Struts2框架中。以下是關於OGNL的一些關鍵特性:
簡單表示式: 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邏輯,以及&&、||等邏輯運算子,使得表示式可以處理更復雜的邏輯判斷。
變數賦值: OGNL不僅能夠讀取資料,還能設定物件屬性的值,如person.name = "Alice"。
安全問題: 和SpEL一樣,使用OGNL時也需注意表示式注入的安全風險,確保使用者輸入不會被直接用於構造表示式,以防止惡意操作。
OGNL以其簡潔的語法和強大的功能,在處理物件關係和資料繫結方面非常實用,尤其是在需要動態操作物件和集合的場景下。
ognl工具類
public class OgnlExpressionUtil {
private OgnlExpressionUtil(){}
/**
* Evaluates the given Ognl EL expression against the provided root object.
*
* @param rootObject The object to use as the root of the expression evaluation.
* @param expressionString The OGNL EL expression to evaluate.
* @param returnType The expected return type.
* @return The result of the expression evaluation.
*/
public static <T> T evaluateExpression(Map<String, Object> rootObject, String expressionString, Class<T> returnType) {
Object value = OgnlCache.getValue(expressionString, rootObject);
if(value != null && value.getClass().isAssignableFrom(returnType)){
return (T)value;
}
return null;
}
public static void main(String[] args) {
Map<String,Object> map = new HashMap<>();
map.put("name","lybgeek");
map.put("hello","world");
System.out.println(OgnlExpressionUtil.evaluateExpression(map,"#root.name",String.class));
System.out.println(SpringExpressionUtil.evaluateExpression(map,"#root.get('hello')",String.class));
}
}
3、Aviator
官方文件
http://fnil.net/aviator/
官方示例
https://github.com/killme2008/aviatorscript
Aviator是一個輕量級的Java表示式執行引擎,它設計用於高效能的動態計算場景,特別是那些需要在執行時解析和執行復雜表示式的應用場景。以下是Aviator的一些核心特點和功能:
高效能: Aviator最佳化了表示式的編譯和執行過程,特別適合於對效能有嚴格要求的系統,如金融風控、實時計算等領域。
易於整合: 提供簡單的API介面,使得在Java專案中嵌入Aviator變得非常容易,只需引入依賴,即可開始編寫和執行表示式。
豐富的表示式支援: 支援數學運算、邏輯運算、比較運算、位運算、字串操作、三元運算、變數定義與引用、函式呼叫等,幾乎覆蓋了所有常見的運算需求。
安全沙箱模式: Aviator提供了沙箱機制,可以限制表示式的執行許可權,比如禁止訪問某些方法或欄位,從而提高應用的安全性。
動態指令碼執行: 允許在執行時動態載入和執行指令碼,非常適合用於規則引擎、配置驅動的系統邏輯等場景。
JIT編譯: Aviator採用即時編譯技術,將表示式編譯成Java位元組碼執行,進一步提升執行效率。
資料繫結: 可以方便地將Java物件、Map、List等資料結構繫結到表示式上下文中,實現表示式與Java資料的無縫對接。
擴充套件性: 支援自定義函式,使用者可以根據需要擴充套件Aviator的功能,增加特定業務邏輯的處理能力。
Aviator因其高效能和靈活性,在需要動態指令碼處理的場景中,特別是在那些對效能敏感且需要頻繁執行復雜計算邏輯的應用中,是一個非常有吸引力的選擇。
Aviator工具類
public final class AviatorExpressionUtil {
private AviatorExpressionUtil() {
}
/**
* 執行Aviator表示式並返回結果
*
* @param expression Aviator表示式字串
* @param env 上下文環境,可以包含變數和函式
* @return 表示式計算後的結果
*/
public static <T> T evaluateExpression(Map<String, Object> env,String expression, Class<T> returnType) {
Object value = AviatorEvaluator.execute(expression, env);
if(value != null && value.getClass().isAssignableFrom(returnType)){
return (T)value;
}
return null;
}
public static void main(String[] args) {
Map<String,Object> map = new HashMap<>();
map.put("name","lybgeek");
map.put("hello","world");
Map<String,Object> env = new HashMap<>();
env.put("root",map);
System.out.println(evaluateExpression(env,"#root.name",String.class));
}
}
4、Mvel2
官方文件
mvel.documentnode.com/
官方示例
https://github.com/mvel/mvel
MVEL2(MVFLEX Expression Language 2)是一種強大且靈活的Java庫,用於解析和執行表示式語言。它是MVEL專案的第二代版本,旨在提供高效、簡潔的方式來操作物件和執行邏輯。下面是關於MVEL2的一些關鍵特性和使用指南:
動態型別與靜態型別混合: MVEL支援動態型別,同時也允許靜態型別檢查,這意味著你可以選擇是否在編譯時檢查型別錯誤,增加了靈活性和安全性。
簡潔的語法: MVEL語法基於Java但更加簡潔,便於編寫和閱讀,適用於快速構建表示式和小型指令碼。
屬性訪問與方法呼叫: 類似於其他表示式語言,MVEL允許直接訪問物件屬性和呼叫其方法,如person.name或list.size()。
控制流語句: 支援if、else、switch、迴圈(for、while)等控制流結構,使得在表示式中實現複雜邏輯成為可能。
模板引擎: MVEL2提供了一個強大的模板引擎,可以用來生成文字輸出,類似於Velocity或Freemarker,但與MVEL表示式無縫整合。
變數賦值與函式定義: 直接在表示式中定義變數和函式,支援區域性變數和閉包(匿名函式)。
資料繫結與轉換: 自動或手動進行型別轉換,簡化了不同資料型別間的操作。
整合與擴充套件: MVEL設計為易於整合到現有Java專案中,同時提供了擴充套件點,允許使用者定義自定義函式和運算子。
效能最佳化: MVEL關注執行效率,透過最佳化的編譯器和執行引擎來減少執行時開銷。
5、Hutool表示式引擎門面
官方文件
https://doc.hutool.cn/pages/ExpressionUtil/#介紹
hutool工具包在5.5.0版本之後,提供了表示式計算引擎封裝為門面模式,提供統一的API,去除差異。目前支援如下表示式引擎
- Aviator
- Apache Jexl3
- MVEL
- JfireEL
- Rhino
- Spring Expression Language
(SpEL)
如上所述的表示式引擎不能滿足要求,hutool還支援透過SPI進行自定義擴充套件
基於hutool封裝的工具類
public class HutoolExpressionUtil {
private HutoolExpressionUtil(){}
/**
* 執行表示式並返回結果。
*
* @param expression 表示式字串
* @param variables 變數對映,鍵為變數名,值為變數值
* @return 表示式計算後的結果
*/
public static <T> T evaluateExpression(Map<String, Object> variables,String expression, Class<T> returnType) {
try {
Object value = ExpressionUtil.eval(expression, variables);
if(value != null && value.getClass().isAssignableFrom(returnType)){
return (T)value;
}
} catch (Exception e) {
throw new RuntimeException("Error executing expression: " + expression, e);
}
return null;
}
public static void main(String[] args) {
Map<String,Object> map = new HashMap<>();
map.put("name","lybgeek");
map.put("hello","world");
Map<String,Object> variables = new HashMap<>();
variables.put("root",map);
System.out.println(evaluateExpression(variables,"root.name",String.class));
}
}
總結
本文介紹了市面比較常用的表示式引擎元件,而這些引擎基本上都可以用hutool提供的表示式門面實現,hutool確實在工具類這方面做得很好,基本上我們日常會用到的工具,它大部分都涵蓋到。最後文末demo連結,也提供了跟spring整合的表達引擎聚合實現,大家感興趣也可以看看。
demo連結
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-el