前言
我們?cè)谠O(shè)計(jì)一些表單或者流程引擎時(shí)铝侵,可能我們會(huì)設(shè)計(jì)各種各樣的表達(dá)式或者規(guī)則,我們通過(guò)各種表達(dá)式或者規(guī)則來(lái)實(shí)現(xiàn)我們的業(yè)務(wù)流轉(zhuǎn)柴信。今天就來(lái)盤點(diǎn)一下我們經(jīng)常會(huì)使用到的表達(dá)式引擎
常用表達(dá)式引擎
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框架中的一個(gè)強(qiáng)大的表達(dá)式語(yǔ)言,用于在運(yùn)行時(shí)查詢和操作對(duì)象圖结执。以下是關(guān)于Spring EL的幾個(gè)關(guān)鍵點(diǎn):
動(dòng)態(tài)查詢和操作: SpEL允許你在運(yùn)行時(shí)執(zhí)行復(fù)雜的查詢和操作數(shù)據(jù),比如讀取bean的屬性值艾凯、調(diào)用方法献幔、進(jìn)行算術(shù)運(yùn)算、邏輯判斷等趾诗。
集成于Spring框架: SpEL廣泛應(yīng)用于Spring的各種模塊中蜡感,如Spring Security的訪問(wèn)控制表達(dá)式、Spring Data的查詢條件定義恃泪、Spring Integration的消息路由等郑兴。
基本語(yǔ)法: SpEL表達(dá)式通常被包含在#{...}中,例如#{property}用來(lái)獲取一個(gè)bean的屬性值贝乎。它支持字符串情连、布爾、算術(shù)览效、關(guān)系却舀、邏輯運(yùn)算符,以及方法調(diào)用锤灿、數(shù)組和列表索引訪問(wèn)等挽拔。
上下文感知: SpEL能夠訪問(wèn)Spring應(yīng)用上下文中的Bean,這意味著你可以直接在表達(dá)式中引用配置的bean衡招,實(shí)現(xiàn)高度靈活的配置和運(yùn)行時(shí)行為調(diào)整篱昔。
類型轉(zhuǎn)換: SpEL提供了內(nèi)置的類型轉(zhuǎn)換服務(wù),可以自動(dòng)或顯式地將一種類型的值轉(zhuǎn)換為另一種類型。
安全考量: 使用SpEL時(shí)需要注意安全性州刽,避免注入攻擊空执。Spring提供了ExpressionParser的配置來(lái)限制表達(dá)式的執(zhí)行能力,如禁用方法調(diào)用或?qū)傩栽L問(wèn)等穗椅。
例子:
- 訪問(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 {
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) 是一個(gè)強(qiáng)大的表達(dá)式語(yǔ)言,用于獲取和設(shè)置Java對(duì)象的屬性匹表。它在許多Java框架中被用作數(shù)據(jù)綁定和操作對(duì)象圖的工具门坷,最著名的應(yīng)用是在Apache Struts2框架中。以下是關(guān)于OGNL的一些關(guān)鍵特性:
簡(jiǎn)單表達(dá)式: OGNL允許你以簡(jiǎn)單的字符串形式編寫表達(dá)式來(lái)訪問(wèn)對(duì)象屬性袍镀,如person.name就可以獲取person對(duì)象的name屬性默蚌。
鏈?zhǔn)綄?dǎo)航: 支持鏈?zhǔn)秸{(diào)用來(lái)深入對(duì)象圖,例如customer.address.street會(huì)依次導(dǎo)航到customer的address屬性苇羡,再?gòu)腶ddress獲取street绸吸。
集合操作: OGNL可以直接在表達(dá)式中處理集合和數(shù)組,包括遍歷设江、篩選锦茁、投影等操作,如customers.{name}可以獲取所有customers集合中每個(gè)元素的name屬性叉存。
上下文敏感: OGNL表達(dá)式解析時(shí)會(huì)考慮一個(gè)上下文環(huán)境码俩,這個(gè)環(huán)境包含了變量、對(duì)象和其他表達(dá)式可能需要的信息歼捏。
方法調(diào)用與構(gòu)造器: 除了屬性訪問(wèn)稿存,OGNL還支持調(diào)用對(duì)象的方法和構(gòu)造新對(duì)象,如@myUtil.trim(name)調(diào)用工具類方法瞳秽,或new java.util.Date()創(chuàng)建新對(duì)象挠铲。
條件與邏輯運(yùn)算: 支持if、else邏輯寂诱,以及&&、||等邏輯運(yùn)算符安聘,使得表達(dá)式可以處理更復(fù)雜的邏輯判斷痰洒。
變量賦值: OGNL不僅能夠讀取數(shù)據(jù),還能設(shè)置對(duì)象屬性的值浴韭,如person.name = "Alice"丘喻。
安全問(wèn)題: 和SpEL一樣,使用OGNL時(shí)也需注意表達(dá)式注入的安全風(fēng)險(xiǎn)念颈,確保用戶輸入不會(huì)被直接用于構(gòu)造表達(dá)式泉粉,以防止惡意操作。
OGNL以其簡(jiǎn)潔的語(yǔ)法和強(qiáng)大的功能,在處理對(duì)象關(guān)系和數(shù)據(jù)綁定方面非常實(shí)用嗡靡,尤其是在需要?jiǎng)討B(tài)操作對(duì)象和集合的場(chǎng)景下跺撼。
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
官方示例
https://github.com/killme2008/aviatorscript
Aviator是一個(gè)輕量級(jí)的Java表達(dá)式執(zhí)行引擎讨彼,它設(shè)計(jì)用于高性能的動(dòng)態(tài)計(jì)算場(chǎng)景歉井,特別是那些需要在運(yùn)行時(shí)解析和執(zhí)行復(fù)雜表達(dá)式的應(yīng)用場(chǎng)景。以下是Aviator的一些核心特點(diǎn)和功能:
高性能: Aviator優(yōu)化了表達(dá)式的編譯和執(zhí)行過(guò)程哈误,特別適合于對(duì)性能有嚴(yán)格要求的系統(tǒng)哩至,如金融風(fēng)控、實(shí)時(shí)計(jì)算等領(lǐng)域蜜自。
易于集成: 提供簡(jiǎn)單的API接口菩貌,使得在Java項(xiàng)目中嵌入Aviator變得非常容易,只需引入依賴重荠,即可開(kāi)始編寫和執(zhí)行表達(dá)式箭阶。
豐富的表達(dá)式支持: 支持?jǐn)?shù)學(xué)運(yùn)算、邏輯運(yùn)算晚缩、比較運(yùn)算尾膊、位運(yùn)算、字符串操作荞彼、三元運(yùn)算冈敛、變量定義與引用、函數(shù)調(diào)用等鸣皂,幾乎覆蓋了所有常見(jiàn)的運(yùn)算需求抓谴。
安全沙箱模式: Aviator提供了沙箱機(jī)制,可以限制表達(dá)式的執(zhí)行權(quán)限寞缝,比如禁止訪問(wèn)某些方法或字段癌压,從而提高應(yīng)用的安全性。
動(dòng)態(tài)腳本執(zhí)行: 允許在運(yùn)行時(shí)動(dòng)態(tài)加載和執(zhí)行腳本荆陆,非常適合用于規(guī)則引擎滩届、配置驅(qū)動(dòng)的系統(tǒng)邏輯等場(chǎng)景。
JIT編譯: Aviator采用即時(shí)編譯技術(shù)被啼,將表達(dá)式編譯成Java字節(jié)碼執(zhí)行帜消,進(jìn)一步提升執(zhí)行效率。
數(shù)據(jù)綁定: 可以方便地將Java對(duì)象浓体、Map泡挺、List等數(shù)據(jù)結(jié)構(gòu)綁定到表達(dá)式上下文中,實(shí)現(xiàn)表達(dá)式與Java數(shù)據(jù)的無(wú)縫對(duì)接命浴。
擴(kuò)展性: 支持自定義函數(shù)娄猫,用戶可以根據(jù)需要擴(kuò)展Aviator的功能贱除,增加特定業(yè)務(wù)邏輯的處理能力。
Aviator因其高性能和靈活性媳溺,在需要?jiǎng)討B(tài)腳本處理的場(chǎng)景中月幌,特別是在那些對(duì)性能敏感且需要頻繁執(zhí)行復(fù)雜計(jì)算邏輯的應(yīng)用中,是一個(gè)非常有吸引力的選擇褂删。
Aviator工具類
public final class AviatorExpressionUtil {
private AviatorExpressionUtil() {
}
/**
* 執(zhí)行Aviator表達(dá)式并返回結(jié)果
*
* @param expression Aviator表達(dá)式字符串
* @param env 上下文環(huán)境飞醉,可以包含變量和函數(shù)
* @return 表達(dá)式計(jì)算后的結(jié)果
*/
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
官方示例
https://github.com/mvel/mvel
MVEL2(MVFLEX Expression Language 2)是一種強(qiáng)大且靈活的Java庫(kù)屯阀,用于解析和執(zhí)行表達(dá)式語(yǔ)言缅帘。它是MVEL項(xiàng)目的第二代版本,旨在提供高效难衰、簡(jiǎn)潔的方式來(lái)操作對(duì)象和執(zhí)行邏輯钦无。下面是關(guān)于MVEL2的一些關(guān)鍵特性和使用指南:
動(dòng)態(tài)類型與靜態(tài)類型混合: MVEL支持動(dòng)態(tài)類型,同時(shí)也允許靜態(tài)類型檢查盖袭,這意味著你可以選擇是否在編譯時(shí)檢查類型錯(cuò)誤失暂,增加了靈活性和安全性。
簡(jiǎn)潔的語(yǔ)法: MVEL語(yǔ)法基于Java但更加簡(jiǎn)潔鳄虱,便于編寫和閱讀弟塞,適用于快速構(gòu)建表達(dá)式和小型腳本。
屬性訪問(wèn)與方法調(diào)用: 類似于其他表達(dá)式語(yǔ)言拙已,MVEL允許直接訪問(wèn)對(duì)象屬性和調(diào)用其方法决记,如person.name或list.size()。
控制流語(yǔ)句: 支持if倍踪、else系宫、switch、循環(huán)(for建车、while)等控制流結(jié)構(gòu)扩借,使得在表達(dá)式中實(shí)現(xiàn)復(fù)雜邏輯成為可能。
模板引擎: MVEL2提供了一個(gè)強(qiáng)大的模板引擎缤至,可以用來(lái)生成文本輸出潮罪,類似于Velocity或Freemarker,但與MVEL表達(dá)式無(wú)縫集成领斥。
變量賦值與函數(shù)定義: 直接在表達(dá)式中定義變量和函數(shù)错洁,支持局部變量和閉包(匿名函數(shù))。
數(shù)據(jù)綁定與轉(zhuǎn)換: 自動(dòng)或手動(dòng)進(jìn)行類型轉(zhuǎn)換戒突,簡(jiǎn)化了不同數(shù)據(jù)類型間的操作。
集成與擴(kuò)展: MVEL設(shè)計(jì)為易于集成到現(xiàn)有Java項(xiàng)目中描睦,同時(shí)提供了擴(kuò)展點(diǎn)膊存,允許用戶定義自定義函數(shù)和操作符。
性能優(yōu)化: MVEL關(guān)注執(zhí)行效率,通過(guò)優(yōu)化的編譯器和執(zhí)行引擎來(lái)減少運(yùn)行時(shí)開(kāi)銷隔崎。
5今艺、Hutool表達(dá)式引擎門面
官方文檔
https://doc.hutool.cn/pages/ExpressionUtil/#介紹
hutool工具包在5.5.0版本之后,提供了表達(dá)式計(jì)算引擎封裝為門面模式爵卒,提供統(tǒng)一的API虚缎,去除差異。目前支持如下表達(dá)式引擎
- Aviator
- Apache Jexl3
- MVEL
- JfireEL
- Rhino
- Spring Expression Language
(SpEL)
如上所述的表達(dá)式引擎不能滿足要求钓株,hutool還支持通過(guò)SPI進(jìn)行自定義擴(kuò)展
基于hutool封裝的工具類
public class HutoolExpressionUtil {
private HutoolExpressionUtil(){}
/**
* 執(zhí)行表達(dá)式并返回結(jié)果实牡。
*
* @param expression 表達(dá)式字符串
* @param variables 變量映射,鍵為變量名轴合,值為變量值
* @return 表達(dá)式計(jì)算后的結(jié)果
*/
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));
}
}
總結(jié)
本文介紹了市面比較常用的表達(dá)式引擎組件创坞,而這些引擎基本上都可以用hutool提供的表達(dá)式門面實(shí)現(xiàn),hutool確實(shí)在工具類這方面做得很好受葛,基本上我們?nèi)粘?huì)用到的工具题涨,它大部分都涵蓋到。最后文末demo鏈接总滩,也提供了跟spring整合的表達(dá)引擎聚合實(shí)現(xiàn)纲堵,大家感興趣也可以看看。
demo鏈接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-el