1. 程式人生 > >利用JEXL實現動態表示式編譯

利用JEXL實現動態表示式編譯

背景

做專案突然遇到這樣的需求:
系統要獲取多個數據源的資料,並進行處理,最後輸出多個欄位。欄位的計算規則一般是簡單的取值最多加一點條件判斷。
而且需要動態變動!!例如一個欄位a的取值,如果a > 10的時候輸出10,a <= 10則輸出a。這裡的10可能在一天後改成8,也可能在後天就改成了12。當然,如果只是一個數字的變動還好說,我們可以使用資料庫進行儲存。但是,萬一哪天需求突然變成了a < 10的時候輸出10,a >=10 則輸出a,就需要對程式碼改動,再測試再發布才能到生產環境使用。
一兩個這樣的欄位還沒什麼,如果整個系統所依賴的欄位都有這樣的屬性,那麼我們就需要找一種方法來實現動態的載入邏輯。
下面介紹的JEXL就可以解決這種問題

JEXL(Java Expression Language)介紹

例項

maven依賴:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-jexl</artifactId>
    <version>2.0</version>
</dependency>

正則表示式匹配

首先寫一個公共方法:

public class Util {
    public static
boolean regMatch(String regEx, String str) { Pattern pattern = Pattern.compile(regEx); return pattern.matcher(str).matches(); } }

下面是使用JEXL呼叫的方法

public void RL() {
    JexlContext jc = new MapContext();
    String str = "一二三四五六七八九十";
    jc.set("Util", new Util());
    jc.set("str"
, str); jc.set("ans", ""); String expression = "ans = Util.regMatch(\"[\u4e00-\u9fa5]{10,}\",str)"; Expression e = new JexlEngine().createExpression(expression); e.evaluate(jc); System.out.println(jc.get("ans")); }

程式碼中的expression變數就是可以動態編譯的表示式,這裡要注意表示式中出現的所有變數,都需要事先set進JexlContext中,否則會報錯。這裡有多種形式的錯誤:
①如果沒有set”Util”,程式執行中會丟擲異常。

org.apache.commons.jexl2.JexlException: [email protected]40![13,40]: 'ans = QeUtil.regMatch('[一-龥]{10,}', str);' attempting to call method on null

②如果沒有set”str”,程式不會丟擲異常,並輸出null。如果你的regMatch方法中有判空處理,就會輸出判空的結果。如果沒有判空處理,在控制檯的輸出如下:

警告: [email protected]39![36,39]: 'ans = QeUtil.regMatch('[一-龥]{10,}', str);' undefined variable str
二月 21, 2017 4:00:41 下午 org.apache.commons.jexl2.JexlEngine invocationFailed
警告: [email protected]39![13,40]: 'ans = QeUtil.regMatch('[一-龥]{10,}', str);' method invocation error
java.lang.NullPointerException

③如果沒有set”ans”,程式會正常執行,並輸出正確值
為了保險起見,建議表示式中出現的所有變數,都需要事先set進JexlContext中

迴圈

JEXL支援兩種迴圈方式:

for(item : list) {
    x = x + item;
}

while (x lt 10) {
    x = x + 2;
}

下面是使用while的例項:

public void loop() {
    JexlContext jc = new MapContext();
    jc.set("a", 1);
    jc.set("b", "0");
    jc.set("ans", new StringBuffer());
    Expression e = new JexlEngine().createExpression("while (a < 10) {a = a + 1;ans.append(b);}");
    e.evaluate(jc);
    System.out.println(jc.get("ans"));
}

get\set方法呼叫

JEXL支援傳入物件,並呼叫物件的方法
下面的簡單的get\set方法的例項:

public void getSet() {
    TmpTest tmpTest = new TmpTest();
    tmpTest.setA(1);
    JexlContext jc = new MapContext();
    jc.set("tmpTest", tmpTest);
    jc.set("ans", "");
    Expression e = new JexlEngine().createExpression("ans = tmpTest.getA()");
    e.evaluate(jc);
    System.out.println(jc.get("ans"));
    e = new JexlEngine().createExpression("ans = tmpTest.setA(2)");
    e.evaluate(jc);
    TmpTest tmpTest1 = (TmpTest) jc.get("tmpTest");
    System.out.println(tmpTest1.getA());
}

上面的用例會在控制檯先輸出1,再輸出2