干掉if else!強烈推薦這五款Java表達式引擎
在進行表單或者流程引擎設計時,我們常常需要構建各種各樣的表達式或者規則,以此來驅動業務流程的順利運轉。這些表達式和規則就如同精密的齒輪,相互協作,讓業務邏輯得以有序執行。今天,我們就來全面盤點一下 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 的配置選項,我們可以通過它來限制表達式的執行能力,比如禁用方法調用或者屬性訪問等。這樣可以有效地防止惡意用戶通過構造惡意表達式來攻擊我們的應用程序。
示例代碼
// 訪問 Bean 屬性
#{myBean.propertyName}
// 方法調用
#{myBean.myMethod(args)}
// 三元運算符
#{condition ? trueValue : falseValue}
// 列表和數組訪問
#{myList[0]}
// 算術運算
#{2 + 3}
SpEL 工具類
public class SpringExpressionUtil {
privatestaticfinal SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
private SpringExpressionUtil() {}
/**
* 針對提供的根對象計算給定的 Spring EL 表達式。
*
* @param rootObject 作為表達式計算根的對象。
* @param expressionString 要計算的 Spring EL 表達式。
* @param returnType 期望的返回類型。
* @return 表達式計算的結果。
*/
publicstatic <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));
}
}
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 工具類
public class OgnlExpressionUtil {
private OgnlExpressionUtil() {}
/**
* 針對提供的根對象計算給定的 Ognl EL 表達式。
*
* @param rootObject 作為表達式計算根的對象。
* @param expressionString 要計算的 OGNL EL 表達式。
* @param returnType 期望的返回類型。
* @return 表達式計算的結果。
*/
publicstatic <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;
}
returnnull;
}
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));
}
}
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 工具類
public finalclass AviatorExpressionUtil {
private AviatorExpressionUtil() {}
/**
* 執行 Aviator 表達式并返回結果
*
* @param expression Aviator 表達式字符串
* @param env 上下文環境,可以包含變量和函數
* @return 表達式計算后的結果
*/
publicstatic <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;
}
returnnull;
}
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));
}
}
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 封裝的工具類
public class HutoolExpressionUtil {
private HutoolExpressionUtil() {}
/**
* 執行表達式并返回結果。
*
* @param expression 表達式字符串
* @param variables 變量映射,鍵為變量名,值為變量值
* @return 表達式計算后的結果
*/
publicstatic <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) {
thrownew RuntimeException("Error executing expression: " + expression, e);
}
returnnull;
}
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 整合的表達式引擎聚合實現,感興趣的讀者可以進一步查看。
希望通過本文的介紹,大家能夠對這些 Java 表達式引擎有更深入的了解,在實際開發中能夠根據具體需求選擇合適的表達式引擎,提高開發效率和代碼質量。