莊周夢蝶

          生活、程序、未來
             :: 首頁 ::  ::  :: 聚合  :: 管理

          Aviator——讓表達式飛起來

          Posted on 2010-06-29 11:44 dennis 閱讀(22081) 評論(15)  編輯  收藏 所屬分類: javamy open-source

              《飛行大亨》是我很喜歡的一部電影,不過這里我想介紹的是一個叫Aviator的開源的Java表達式求值器。

          一、輪子的必要性

              表達式的求值上,java的選擇非常多,強大的如Groovy、JRuby,N年沒維護的beanshell,包括javaeye上朋友的IKExpression。為什么還需要Aviator?或者說Aviator的特點是什么?

              我將Aviator定位在Groovy這樣全功能的腳本和IKExpression這樣的簡易的表達式求值之間的東西,如果你不希望帶上Groovy那么龐大的jar卻只用上一點點的功能,如果你希望功能和性能上比IKExpression好那么一些,那么也許你可以考慮Aviator。

              Aviator的設計思路跟利用GroovyObject的求值是一樣,通過編譯并動態生成字節碼的方式將表達式編譯成一個類,然后反射執行這個類,因此會在效率上比純解釋執行的IKExpression好一些。

           aviator結構圖

          二、讓輪子轉起來。

          求算術表達式: 

          import com.googlecode.aviator.AviatorEvaluator;


          public class SimpleExample {
              
          public static void main(String[] args) {
                  Long result 
          = (Long) AviatorEvaluator.execute("1+2+3");
                  System.out.println(result);
              }

          }

           

          執行入口統一為AviatorEvaluator類,它有一系列靜態方法。 

          邏輯表達式和關系運算

          AviatorEvaluator.execute("3>1 && 2!=4 || true");

          Aviator支持所有的關系運算符和算術運算符,不支持位運算,同時支持表達式的優先級,優先級跟Java的運算符一樣,并且支持通過括號來強制優先級。 

          使用變量和字符串相加

                 String yourname = “aviator”;
                  Map
          <String, Object> env = new HashMap<String, Object>();
                  env.put(
          "yourname", yourname);
                  String result 
          = (String) AviatorEvaluator.execute(" 'hello ' + yourname ", env);
                  System.out.println(result);

           打印:

          hello aviator


          字符串可以單引號也可以雙引號括起來,并且支持轉義字符。變量名稱只要是合法的java identifer即可,變量需要用戶傳入,通過Map<String,Object>指定變量名和值是什么,這里的變量是yourname。 

          變量的訪問支持嵌套訪問,也就是dot操作符來訪問變量里的屬性,假設我們有一個Foo類:

          public static class Foo {
                  
          int i;
                  
          float f;
                  Date date 
          = new Date();

                  
          public Foo(int i, float f, Date date) {
                      
          super();
                      
          this.i = i;
                      
          this.f = f;
                      
          this.date = date;
                  }

                  
          public int getI() {
                      
          return i;
                  }

                  
          public void setI(int i) {
                      
          this.i = i;
                  }

                  
          public float getF() {
                      
          return f;
                  }

                  
          public void setF(float f) {
                      
          this.f = f;
                  }

                  
          public Date getDate() {
                      
          return date;
                  }

                  
          public void setDate(Date date) {
                      
          this.date = date;
                  }


              }

            然后在使用一個表達式來描述Foo里的各種屬性:

          Foo foo = new Foo(1003.14fnew Date());
                  Map
          <String, Object> env = new HashMap<String, Object>();
                  env.put(
          "foo", foo);

                  String result 
          =
                          (String) AviatorEvaluator.execute(
                              
          " '[foo i='+ foo.i + ' f='+foo.f+' year='+(foo.date.year+1900)+ ' month='+foo.date.month +']' ",
                              env);


           我們可以通過foo.date.year的方式來訪問變量foo中date屬性的year值,這是利用commons-beanutils的反射功能實現的,前提是你的變量是合法的JavaBean(public、getter缺一不可)。 

          三元表達式

          AviatorEvaluator.execute("3>0? 'yes':'no'");


          上面都還是一個求值器表達式的常見功能,下面要描述的是Aviator的一些偏腳本性的功能。

           類Ruby、Perl的正則匹配,匹配email地址:

          AviatorEvaluator.execute("'killme2008'=~/([\\w0-8]+@\\w+[\\.\\w+]+)/ ");

            成功的話返回true,否則返回false。//括起來的字符序列形成一個正則表達式Pattern類型,=~用于匹配,只能在String和Pattern之間使用。

          匹配成功,獲得匹配的分組,利用變量$digit

          AviatorEvaluator.execute("'killme2008@gmail.com'=~/([\\w0-8]+@\\w+[\\.\\w+]+)/ ? $1:'unknow'");

           匹配成功返回$1,表示第一個匹配的分組,也就是用戶名 killme2008 

          函數調用

          AviatorEvaluator.execute("sysdate()");

           sysdate()是一個內置函數,返回當前日期,跟new java.util.Date()效果相同。

           更多內置函數:

          AviatorEvaluator.execute("string.length('hello')");    // 求字符串長度
          AviatorEvaluator.execute("string.contains('hello','h')");  //判斷字符串是否包含字符串
          AviatorEvaluator.execute("string.startsWith('hello','h')");  //是否以子串開頭
          AviatorEvaluator.execute("string.endsWith('hello','llo')");  是否以子串結尾

          AviatorEvaluator.execute(
          "math.pow(-3,2)");   // 求n次方
          AviatorEvaluator.execute("math.sqrt(14.0)");   //開平方根
          AviatorEvaluator.execute("math.sin(20)");    //正弦函數

          可以看到Aviator的函數調用風格非常類似lua或者c。

           自定義函數,實現AviatorFunction接口并注冊即可,比如我們實現一個add函數用于相加:

          import com.googlecode.aviator.AviatorEvaluator;
          import com.googlecode.aviator.runtime.function.FunctionUtils;
          import com.googlecode.aviator.runtime.type.AviatorDouble;
          import com.googlecode.aviator.runtime.type.AviatorFunction;
          import com.googlecode.aviator.runtime.type.AviatorObject;
          class AddFunction implements AviatorFunction {

                  
          public AviatorObject call(Map<String, Object> env, AviatorObject args) {
                      
          if (args.length != 2{
                          
          throw new IllegalArgumentException("Add only supports two arguments");
                      }

                      Number left 
          = FunctionUtils.getNumberValue(0, args, env);
                      Number right 
          = FunctionUtils.getNumberValue(1, args, env);
                      
          return new AviatorDouble(left.doubleValue() + right.doubleValue());
                  }



                  
          public String getName() {
                      
          return "add";
                  }


              }

          注冊并調用:

             AviatorEvaluator.addFunction(new AddFunction());
                  System.out.println(AviatorEvaluator.execute(
          "add(1,2)"));
                  System.out.println(AviatorEvaluator.execute(
          "add(add(1,2),100)"));

          函數可以嵌套調用。

           三、不公平的性能測試

             基本介紹完了,最后給些測試的數據,下列的測試場景都是每個表達式預先編譯,然后執行1000萬次,測量執行耗時。

           場景1:

          算術表達式   1000+100.0*99-(600-3*15)/(((68-9)-3)*2-100)+10000%7*71

          結果:

          測試 耗時(單位:秒)
          Aviator 14.0
          Groovy 79.6
          IKExpression 159.2

           

          場景2:
          計算邏輯表達式和三元表達式混合: 6.7-100>39.6 ? 5==5? 4+5:6-1 : !(100%3-39.0<27) ? 8*2-199: 100%3

          測試結果:

          測試 耗時(單位:秒)
          Aviator 11.0
          Groovy 13.0
          IKExpression 168.8

           

          場景3:

          計算算術表達式和邏輯表達式的混合,帶有5個變量的表達式: 

          * pi + (d * b - 199/ (1 - d * pi) - (2 + 100 - i / pi) % 99 ==* pi + (d * b - 199/ (1 - d * pi) - (2 + 100 - i / pi) % 99

           變量設定為:

                  int i = 100;
                  
          float pi = 3.14f;
                  
          double d = -3.9;
                  
          byte b = (byte4;
                  
          boolean bool=false;

          每次執行前都重新設置這些變量的值。

          結果:

          測試 耗時(單位:秒)
          Aviator 31.2
          Groovy 9.7
          IKExpression 編譯錯誤

           場景4:

          • Aviator執行 sysdate()
          • groovy執行 new java.util.Date()
          • IKExpression執行 $SYSDATE()

          結果:

          測試 耗時(單位:秒)
          Aviator 22.6
          Groovy 13.9
          IKExpression 25.4

            原始的測試報告在這里。 

          四、結語

               能看到這里,并且感興趣的朋友請點擊項目主頁:

          http://code.google.com/p/aviator/

            下載地址:

          http://code.google.com/p/aviator/downloads/list

           完整的用戶手冊:

           http://code.google.com/p/aviator/wiki/User_Guide_zh

          目前版本仍然是1.0.0-RC,希望更多朋友試用并最終release。有什么疑問或者建議請跟貼。

           

           

           


          評論

          # re: Aviator——讓表達式飛起來  回復  更多評論   

          2010-06-29 16:48 by jaedong
          大蝦,這個是你寫的嗎,你怎么這么有時間啊,拜讀源碼.

          # re: Aviator——讓表達式飛起來  回復  更多評論   

          2010-06-29 18:00 by dennis
          @jaedong
          是我的作品,感謝關注。

          # re: Aviator——讓表達式飛起來  回復  更多評論   

          2010-06-29 19:16 by cd
          nice~~I like it

          # re: Aviator——讓表達式飛起來  回復  更多評論   

          2010-06-29 20:56 by guest
          支持 JDK 1.4?

          # re: Aviator——讓表達式飛起來  回復  更多評論   

          2010-06-29 21:00 by guest
          @guest

          Oh my gosh, no support for jdk 1.4...

          # re: Aviator——讓表達式飛起來  回復  更多評論   

          2010-06-29 22:54 by 隔葉黃鶯
          很不錯的,以后我想一定也用得著的。

          # re: Aviator——讓表達式飛起來  回復  更多評論   

          2010-06-30 09:20 by @joe
          java的scriptEngine同樣可以做到。。。。

          # re: Aviator——讓表達式飛起來  回復  更多評論   

          2010-06-30 09:20 by @joe
          java的scriptEngine同樣可以做到。。。。

          # re: Aviator——讓表達式飛起來  回復  更多評論   

          2010-06-30 10:13 by Lancelot
          用Spring Expression Language就是了(比這個還要更強大),為什么還要用這個???

          # re: Aviator——讓表達式飛起來  回復  更多評論   

          2010-06-30 10:22 by dennis
          @Lancelot
          恩,spring el是個選擇,我已經提到,avaitor的定位是 spring el,groovy這樣強大的腳本語言與ikexpression這樣的簡易引擎之間的東西。強大的東西很多功能你用不上,簡易的又不能滿足要求,這種時候也許可以考慮下aviator。

          # re: Aviator——讓表達式飛起來  回復  更多評論   

          2010-06-30 10:23 by dennis
          @@joe
          script engine的調用也是委托給腳本語言,script engine的性能不怎么樣,已有的幾個實現來說。如果你用在生產環境的話,基本不靠譜,自己玩玩還行。

          # re: Aviator——讓表達式飛起來[未登錄]  回復  更多評論   

          2011-03-24 18:17 by littleJava
          mvel也是一款表達式解析器,挺輕量級的

          # re: Aviator——讓表達式飛起來  回復  更多評論   

          2011-07-11 00:28 by anvoy
          很好用,剛成功應用到一個之前做的項目中,反應良好,繼續關注

          # re: Aviator——讓表達式飛起來  回復  更多評論   

          2011-09-27 00:50 by saiky
          你好,請問aviator能否將execute過程中匹配的變量名字及其值獲取?例如你的userguide的例子:
          public static void main(String[] args) {
          String expression = "a-(b-c)>100"; // 編譯表達式
          Expression compiledExp = AviatorEvaluator.compile(expression); Map<String, Object> env = new HashMap<String, Object>();
          env.put("a", 100.3);
          env.put("b", 45);
          env.put("c", -199.100);
          // 執行表達式
          Boolean result = (Boolean) compiledExp.execute(env); System.out.println(result);
          }

          能否獲知到底是a、b還是a、c這個變量被使用了,它們的值是多少?多謝
          saiky.liu#gmail.com

          # re: Aviator——讓表達式飛起來  回復  更多評論   

          2016-07-27 22:35 by welcomezhang
          很好用,剛用到最近的一個項目中
          主站蜘蛛池模板: 庆安县| 南皮县| 家居| 三门峡市| 收藏| 连城县| 浪卡子县| 太和县| 嘉义市| 尼勒克县| 涪陵区| 灌南县| 松滋市| 中阳县| 本溪市| 界首市| 云浮市| 庆阳市| 南皮县| 顺义区| 广昌县| 拉萨市| 米脂县| 咸阳市| 锡林郭勒盟| 芒康县| 大荔县| 黄山市| 武川县| 涟源市| 德兴市| 望谟县| 祁门县| 怀化市| 尚义县| 无棣县| 嘉义县| 松江区| 丘北县| 巴彦县| 潜山县|