干掉if else!強(qiáng)烈推薦這五款Java表達(dá)式引擎
在進(jìn)行表單或者流程引擎設(shè)計(jì)時(shí),我們常常需要構(gòu)建各種各樣的表達(dá)式或者規(guī)則,以此來(lái)驅(qū)動(dòng)業(yè)務(wù)流程的順利運(yùn)轉(zhuǎn)。這些表達(dá)式和規(guī)則就如同精密的齒輪,相互協(xié)作,讓業(yè)務(wù)邏輯得以有序執(zhí)行。今天,我們就來(lái)全面盤(pán)點(diǎn)一下 Java 開(kāi)發(fā)中常用的那些表達(dá)式引擎。
這些表達(dá)式引擎在 Java 開(kāi)發(fā)領(lǐng)域應(yīng)用廣泛,相信很多開(kāi)發(fā)者都對(duì)它們有所了解。接下來(lái),讓我們一起重新深入認(rèn)識(shí)一下它們。
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 框架中一項(xiàng)強(qiáng)大的功能,它為我們?cè)谶\(yùn)行時(shí)查詢和操作對(duì)象圖提供了便捷且高效的方式。以下是 SpEL 的幾個(gè)核心特性:
- 動(dòng)態(tài)數(shù)據(jù)處理能力:SpEL 允許我們?cè)谶\(yùn)行時(shí)執(zhí)行復(fù)雜的數(shù)據(jù)查詢和操作。無(wú)論是讀取 bean 的屬性值、調(diào)用方法,還是進(jìn)行算術(shù)運(yùn)算、邏輯判斷,SpEL 都能輕松應(yīng)對(duì)。這使得我們的應(yīng)用程序能夠根據(jù)不同的運(yùn)行時(shí)條件靈活地處理數(shù)據(jù)。
- 與 Spring 框架深度集成:SpEL 廣泛應(yīng)用于 Spring 的各個(gè)模塊中。在 Spring Security 里,它用于定義訪問(wèn)控制表達(dá)式,幫助我們實(shí)現(xiàn)細(xì)粒度的權(quán)限管理;在 Spring Data 中,可用于查詢條件的定義,簡(jiǎn)化數(shù)據(jù)查詢操作;在 Spring Integration 里,還能實(shí)現(xiàn)消息路由的功能,確保消息準(zhǔn)確地傳遞到目標(biāo)位置。
- 獨(dú)特的語(yǔ)法結(jié)構(gòu):SpEL 表達(dá)式通常被包裹在
#{...}
之中。例如,#{property}
可以用來(lái)獲取一個(gè) bean 的屬性值。它支持豐富的運(yùn)算符,包括字符串、布爾、算術(shù)、關(guān)系、邏輯運(yùn)算符,同時(shí)還支持方法調(diào)用、數(shù)組和列表索引訪問(wèn)等操作。這種簡(jiǎn)潔而強(qiáng)大的語(yǔ)法結(jié)構(gòu),使得我們可以用簡(jiǎn)潔的代碼實(shí)現(xiàn)復(fù)雜的邏輯。 - 上下文感知特性:SpEL 能夠敏銳地感知 Spring 應(yīng)用上下文里的 Bean。這意味著我們可以直接在表達(dá)式中引用配置好的 bean,從而實(shí)現(xiàn)高度靈活的配置和運(yùn)行時(shí)行為調(diào)整。例如,我們可以在表達(dá)式中引用一個(gè)服務(wù) bean,調(diào)用其方法來(lái)完成特定的業(yè)務(wù)邏輯。
- 智能類型轉(zhuǎn)換服務(wù):SpEL 提供了內(nèi)置的類型轉(zhuǎn)換服務(wù),它可以自動(dòng)或者根據(jù)我們的顯式要求,將一種類型的值轉(zhuǎn)換為另一種類型。這在處理不同類型的數(shù)據(jù)時(shí)非常方便,避免了我們手動(dòng)進(jìn)行類型轉(zhuǎn)換的繁瑣操作。
- 安全防護(hù)機(jī)制:在使用 SpEL 時(shí),安全性是我們必須要考慮的因素。為了避免注入攻擊,Spring 提供了 ExpressionParser 的配置選項(xiàng),我們可以通過(guò)它來(lái)限制表達(dá)式的執(zhí)行能力,比如禁用方法調(diào)用或者屬性訪問(wèn)等。這樣可以有效地防止惡意用戶通過(guò)構(gòu)造惡意表達(dá)式來(lái)攻擊我們的應(yīng)用程序。
示例代碼
// 訪問(wèn) Bean 屬性
#{myBean.propertyName}
// 方法調(diào)用
#{myBean.myMethod(args)}
// 三元運(yùn)算符
#{condition ? trueValue : falseValue}
// 列表和數(shù)組訪問(wèn)
#{myList[0]}
// 算術(shù)運(yùn)算
#{2 + 3}
SpEL 工具類
public class SpringExpressionUtil {
privatestaticfinal SpelExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
private SpringExpressionUtil() {}
/**
* 針對(duì)提供的根對(duì)象計(jì)算給定的 Spring EL 表達(dá)式。
*
* @param rootObject 作為表達(dá)式計(jì)算根的對(duì)象。
* @param expressionString 要計(jì)算的 Spring EL 表達(dá)式。
* @param returnType 期望的返回類型。
* @return 表達(dá)式計(jì)算的結(jié)果。
*/
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)是一種強(qiáng)大的表達(dá)式語(yǔ)言,專門(mén)用于獲取和設(shè)置 Java 對(duì)象的屬性。它在許多 Java 框架中都扮演著重要的角色,尤其是在 Apache Struts2 框架中,被廣泛應(yīng)用于數(shù)據(jù)綁定和操作對(duì)象圖。
關(guān)鍵特性
- 簡(jiǎn)潔的表達(dá)式語(yǔ)法:OGNL 允許我們以極其簡(jiǎn)單的字符串形式編寫(xiě)表達(dá)式來(lái)訪問(wèn)對(duì)象屬性。例如,
person.name
就可以輕松獲取person
對(duì)象的name
屬性。這種簡(jiǎn)潔的語(yǔ)法使得代碼的編寫(xiě)和閱讀都變得非常容易。 - 強(qiáng)大的鏈?zhǔn)綄?dǎo)航功能:它支持鏈?zhǔn)秸{(diào)用,讓我們可以深入對(duì)象圖進(jìn)行操作。比如,
customer.address.street
會(huì)依次導(dǎo)航到customer
的address
屬性,再?gòu)?nbsp;address
獲取street
屬性。通過(guò)鏈?zhǔn)綄?dǎo)航,我們可以方便地訪問(wèn)對(duì)象的深層屬性。 - 靈活的集合操作能力:OGNL 能夠直接在表達(dá)式中處理集合和數(shù)組,包括遍歷、篩選、投影等操作。例如,
customers.{name}
可以獲取所有customers
集合中每個(gè)元素的name
屬性。這使得我們?cè)谔幚砑蠑?shù)據(jù)時(shí)更加高效。 - 上下文敏感特性:在解析 OGNL 表達(dá)式時(shí),會(huì)充分考慮一個(gè)上下文環(huán)境,這個(gè)環(huán)境包含了變量、對(duì)象以及其他表達(dá)式可能需要的信息。通過(guò)上下文環(huán)境,我們可以在表達(dá)式中引用其他變量和對(duì)象,實(shí)現(xiàn)更加靈活的邏輯處理。
- 豐富的方法與構(gòu)造器支持:除了屬性訪問(wèn),OGNL 還支持調(diào)用對(duì)象的方法和構(gòu)造新對(duì)象。比如,
@myUtil.trim(name)
可以調(diào)用工具類方法,new java.util.Date()
則可以創(chuàng)建新對(duì)象。這為我們?cè)诒磉_(dá)式中實(shí)現(xiàn)復(fù)雜的邏輯提供了更多的可能性。 - 全面的邏輯運(yùn)算支持:它支持
if
、else
邏輯,以及&&
、||
等邏輯運(yùn)算符,使得表達(dá)式能夠處理更為復(fù)雜的邏輯判斷。例如,我們可以使用if
語(yǔ)句來(lái)根據(jù)不同的條件執(zhí)行不同的操作。 - 便捷的變量賦值功能:OGNL 不僅能夠讀取數(shù)據(jù),還能設(shè)置對(duì)象屬性的值。例如,
person.name = "Alice"
就可以為person
對(duì)象的name
屬性賦值。這使得我們可以在表達(dá)式中直接修改對(duì)象的屬性。 - 安全風(fēng)險(xiǎn)防范意識(shí):和 SpEL 一樣,在使用 OGNL 時(shí),我們也需要格外注意表達(dá)式注入的安全風(fēng)險(xiǎn)。要確保用戶輸入不會(huì)被直接用于構(gòu)造表達(dá)式,從而防止惡意操作。例如,我們可以對(duì)用戶輸入進(jìn)行嚴(yán)格的驗(yàn)證和過(guò)濾。
OGNL 工具類
public class OgnlExpressionUtil {
private OgnlExpressionUtil() {}
/**
* 針對(duì)提供的根對(duì)象計(jì)算給定的 Ognl EL 表達(dá)式。
*
* @param rootObject 作為表達(dá)式計(jì)算根的對(duì)象。
* @param expressionString 要計(jì)算的 OGNL EL 表達(dá)式。
* @param returnType 期望的返回類型。
* @return 表達(dá)式計(jì)算的結(jié)果。
*/
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 是一款輕量級(jí)的 Java 表達(dá)式執(zhí)行引擎,專門(mén)為高性能的動(dòng)態(tài)計(jì)算場(chǎng)景而設(shè)計(jì)。它特別適用于那些需要在運(yùn)行時(shí)解析和執(zhí)行復(fù)雜表達(dá)式的應(yīng)用場(chǎng)景。
核心特點(diǎn)
- 卓越的性能表現(xiàn):Aviator 對(duì)表達(dá)式的編譯和執(zhí)行過(guò)程進(jìn)行了深度優(yōu)化,特別適合對(duì)性能有嚴(yán)格要求的系統(tǒng),如金融風(fēng)控、實(shí)時(shí)計(jì)算等領(lǐng)域。在這些領(lǐng)域中,系統(tǒng)需要快速地處理大量的表達(dá)式,Aviator 的高性能可以滿足這些需求。
- 輕松的集成體驗(yàn):它提供了簡(jiǎn)單易用的 API 接口,讓我們?cè)?Java 項(xiàng)目中嵌入 Aviator 變得輕而易舉。只需引入依賴,就可以開(kāi)始編寫(xiě)和執(zhí)行表達(dá)式。這大大降低了我們使用 Aviator 的門(mén)檻。
- 豐富的表達(dá)式支持:Aviator 支持幾乎所有常見(jiàn)的運(yùn)算需求,包括數(shù)學(xué)運(yùn)算、邏輯運(yùn)算、比較運(yùn)算、位運(yùn)算、字符串操作、三元運(yùn)算、變量定義與引用、函數(shù)調(diào)用等。這種豐富的表達(dá)式支持使得我們可以用 Aviator 實(shí)現(xiàn)各種復(fù)雜的業(yè)務(wù)邏輯。
- 安全的沙箱機(jī)制:Aviator 提供了沙箱機(jī)制,我們可以通過(guò)它來(lái)限制表達(dá)式的執(zhí)行權(quán)限,比如禁止訪問(wèn)某些方法或字段,從而大大提高應(yīng)用的安全性。在處理用戶輸入的表達(dá)式時(shí),沙箱機(jī)制可以有效地防止惡意代碼的執(zhí)行。
- 動(dòng)態(tài)腳本執(zhí)行能力:它允許在運(yùn)行時(shí)動(dòng)態(tài)加載和執(zhí)行腳本,這一特性使得它非常適合用于規(guī)則引擎、配置驅(qū)動(dòng)的系統(tǒng)邏輯等場(chǎng)景。我們可以根據(jù)不同的業(yè)務(wù)需求,動(dòng)態(tài)地加載和執(zhí)行不同的腳本。
- JIT 編譯加速技術(shù):Aviator 采用即時(shí)編譯技術(shù),將表達(dá)式編譯成 Java 字節(jié)碼執(zhí)行,進(jìn)一步提升了執(zhí)行效率。通過(guò) JIT 編譯,表達(dá)式的執(zhí)行速度可以得到顯著提高。
- 便捷的數(shù)據(jù)綁定功能:我們可以方便地將 Java 對(duì)象、Map、List 等數(shù)據(jù)結(jié)構(gòu)綁定到表達(dá)式上下文中,實(shí)現(xiàn)表達(dá)式與 Java 數(shù)據(jù)的無(wú)縫對(duì)接。這使得我們?cè)诒磉_(dá)式中可以直接使用 Java 數(shù)據(jù),提高了開(kāi)發(fā)效率。
- 強(qiáng)大的擴(kuò)展能力:Aviator 支持自定義函數(shù),用戶可以根據(jù)自己的需求擴(kuò)展其功能,增加特定業(yè)務(wù)邏輯的處理能力。例如,我們可以自定義一個(gè)函數(shù)來(lái)實(shí)現(xiàn)特定的業(yè)務(wù)算法。
Aviator 工具類
public finalclass AviatorExpressionUtil {
private AviatorExpressionUtil() {}
/**
* 執(zhí)行 Aviator 表達(dá)式并返回結(jié)果
*
* @param expression Aviator 表達(dá)式字符串
* @param env 上下文環(huán)境,可以包含變量和函數(shù)
* @return 表達(dá)式計(jì)算后的結(jié)果
*/
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)是一個(gè)強(qiáng)大且靈活的 Java 庫(kù),用于解析和執(zhí)行表達(dá)式語(yǔ)言。它是 MVEL 項(xiàng)目的第二代版本,旨在提供高效、簡(jiǎn)潔的方式來(lái)操作對(duì)象和執(zhí)行邏輯。
關(guān)鍵特性與使用指南
- 動(dòng)態(tài)類型與靜態(tài)類型混合支持:MVEL 支持動(dòng)態(tài)類型,同時(shí)也允許靜態(tài)類型檢查。這意味著我們可以根據(jù)實(shí)際需求選擇是否在編譯時(shí)檢查類型錯(cuò)誤,增加了代碼的靈活性和安全性。例如,在一些快速開(kāi)發(fā)的場(chǎng)景中,我們可以使用動(dòng)態(tài)類型來(lái)提高開(kāi)發(fā)效率;而在對(duì)類型安全要求較高的場(chǎng)景中,我們可以使用靜態(tài)類型檢查來(lái)避免類型錯(cuò)誤。
- 簡(jiǎn)潔的語(yǔ)法結(jié)構(gòu):MVEL 語(yǔ)法基于 Java 但更加簡(jiǎn)潔,便于編寫(xiě)和閱讀。它適用于快速構(gòu)建表達(dá)式和小型腳本。例如,我們可以用更簡(jiǎn)潔的代碼來(lái)實(shí)現(xiàn)相同的邏輯,減少代碼的冗余。
- 便捷的屬性訪問(wèn)與方法調(diào)用:類似于其他表達(dá)式語(yǔ)言,MVEL 允許直接訪問(wèn)對(duì)象屬性和調(diào)用其方法。例如,
person.name
可以訪問(wèn)person
對(duì)象的name
屬性,list.size()
可以調(diào)用list
對(duì)象的size()
方法。這使得我們?cè)诓僮鲗?duì)象時(shí)更加方便。 - 豐富的控制流語(yǔ)句支持:MVEL 支持
if
、else
、switch
、循環(huán)(for
、while
)等控制流結(jié)構(gòu),使得在表達(dá)式中實(shí)現(xiàn)復(fù)雜邏輯成為可能。我們可以根據(jù)不同的條件執(zhí)行不同的操作,或者對(duì)集合進(jìn)行遍歷操作。 - 強(qiáng)大的模板引擎功能:MVEL2 提供了一個(gè)強(qiáng)大的模板引擎,可以用來(lái)生成文本輸出。它類似于 Velocity 或 Freemarker,但與 MVEL 表達(dá)式無(wú)縫集成。我們可以使用模板引擎來(lái)生成動(dòng)態(tài)的文本內(nèi)容,如郵件模板、報(bào)表等。
- 靈活的變量賦值與函數(shù)定義:MVEL 允許直接在表達(dá)式中定義變量和函數(shù),支持局部變量和閉包(匿名函數(shù))。同時(shí),它還能自動(dòng)或手動(dòng)進(jìn)行類型轉(zhuǎn)換,簡(jiǎn)化了不同數(shù)據(jù)類型間的操作。例如,我們可以在表達(dá)式中定義一個(gè)局部變量,并在后續(xù)的代碼中使用它。
- 良好的集成與擴(kuò)展能力:MVEL 設(shè)計(jì)為易于集成到現(xiàn)有 Java 項(xiàng)目中,同時(shí)提供了擴(kuò)展點(diǎn),允許用戶定義自定義函數(shù)和操作符。這使得我們可以根據(jù)項(xiàng)目的需求對(duì) MVEL 進(jìn)行擴(kuò)展,增加其功能。
- 高效的性能優(yōu)化:MVEL 關(guān)注執(zhí)行效率,通過(guò)優(yōu)化的編譯器和執(zhí)行引擎來(lái)減少運(yùn)行時(shí)開(kāi)銷。在處理大量的表達(dá)式時(shí),MVEL 的高性能可以保證系統(tǒng)的響應(yīng)速度。
Hutool 表達(dá)式引擎門(mén)面
官方文檔
https://doc.hutool.cn/pages/ExpressionUtil/#%E4%BB%8B%E7%BB%8D
Hutool 工具包在 5.5.0 版本之后,將表達(dá)式計(jì)算引擎封裝為門(mén)面模式,提供統(tǒng)一的 API,去除了不同表達(dá)式引擎之間的差異。目前,它支持以下幾種表達(dá)式引擎:
- Aviator
- Apache Jexl3
- MVEL
- JfireEL
- Rhino
- Spring Expression Language (SpEL)
如果上述的表達(dá)式引擎不能滿足我們的需求,Hutool 還支持通過(guò) SPI 進(jìn)行自定義擴(kuò)展。這使得我們可以根據(jù)具體的業(yè)務(wù)需求,靈活地選擇和擴(kuò)展表達(dá)式引擎。
基于 Hutool 封裝的工具類
public class HutoolExpressionUtil {
private HutoolExpressionUtil() {}
/**
* 執(zhí)行表達(dá)式并返回結(jié)果。
*
* @param expression 表達(dá)式字符串
* @param variables 變量映射,鍵為變量名,值為變量值
* @return 表達(dá)式計(jì)算后的結(jié)果
*/
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));
}
}
總結(jié)
本文詳細(xì)介紹了市面上比較常用的幾種表達(dá)式引擎組件。這些引擎各有特點(diǎn),適用于不同的應(yīng)用場(chǎng)景。而 Hutool 提供的表達(dá)式門(mén)面模式,為我們使用這些表達(dá)式引擎提供了統(tǒng)一的接口,大大簡(jiǎn)化了開(kāi)發(fā)過(guò)程。
Hutool 在工具類方面確實(shí)表現(xiàn)出色,幾乎涵蓋了我們?nèi)粘i_(kāi)發(fā)中所需的大部分工具。
最后,文末的 demo 鏈接還提供了與 Spring 整合的表達(dá)式引擎聚合實(shí)現(xiàn),感興趣的讀者可以進(jìn)一步查看。
希望通過(guò)本文的介紹,大家能夠?qū)@些 Java 表達(dá)式引擎有更深入的了解,在實(shí)際開(kāi)發(fā)中能夠根據(jù)具體需求選擇合適的表達(dá)式引擎,提高開(kāi)發(fā)效率和代碼質(zhì)量。