聊聊我們那些年用過(guò)的表達(dá)式引擎組件

前言

我們?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

官方文檔
http://fnil.net/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

官方文檔
mvel.documentnode.com/

官方示例
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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末闰渔,一起剝皮案震驚了整個(gè)濱河市席函,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌澜建,老刑警劉巖向挖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異炕舵,居然都是意外死亡何之,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門咽筋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)溶推,“玉大人,你說(shuō)我怎么就攤上這事奸攻∷馕#” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵睹耐,是天一觀的道長(zhǎng)辐赞。 經(jīng)常有香客問(wèn)我,道長(zhǎng)硝训,這世上最難降的妖魔是什么响委? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任新思,我火速辦了婚禮,結(jié)果婚禮上赘风,老公的妹妹穿的比我還像新娘夹囚。我一直安慰自己,他們只是感情好邀窃,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布荸哟。 她就那樣靜靜地躺著,像睡著了一般瞬捕。 火紅的嫁衣襯著肌膚如雪鞍历。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天山析,我揣著相機(jī)與錄音堰燎,去河邊找鬼。 笑死笋轨,一個(gè)胖子當(dāng)著我的面吹牛秆剪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播爵政,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼仅讽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了钾挟?” 一聲冷哼從身側(cè)響起洁灵,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掺出,沒(méi)想到半個(gè)月后徽千,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汤锨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年双抽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片闲礼。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡牍汹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出柬泽,到底是詐尸還是另有隱情慎菲,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布锨并,位于F島的核電站露该,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏第煮。R本人自食惡果不足惜解幼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一闸拿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧书幕,春花似錦、人聲如沸揽趾。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)篱瞎。三九已至苟呐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間俐筋,已是汗流浹背牵素。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留澄者,地道東北人笆呆。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像粱挡,于是被迫代替她去往敵國(guó)和親赠幕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容