Java結合Groovy讓程序支持動態算法打賞

最近項目在做一個度量平臺,項目目標是整合大量數據,結合各種度量指標的算法,以圖表等形式展現數據優劣趨勢等。

至于前臺的實現技術、架構等內容不在我們討論范圍內,直接忽略,后臺系統架構則采用純Java的后臺,結合多線程、Quartz定時器等技術實現采集、計算,但只是實現了預定義指標、算法的計算(使用系統預定義算法,即程序固定寫死的算法)。說這么多,大家應該發現了,問題就在這,大多比較強大的度量系統,肯定有一套自己獨有的算法規則,可以使用定義好的規則自定義算法,而我們的系統則是一成不變的固定算法,即便說可以添加,也是改Java代碼實現,帶來的工作量可是不小,而且系統會越來越龐大,很難維護。

廢話不說,下面就大概聊一下這里要出廠的主角——Groovy,Groovy是一種基于JVM(Java虛擬機)的敏捷開發語言,它結合了Python、Ruby和Smalltalk的許多強大的特性,Groovy 代碼能夠與 Java 代碼很好地結合,也能用于擴展現有代碼。由于其運行在 JVM 上的特性,Groovy 可以使用其他 Java 語言編寫的庫。看樣子是很誘人,而且還可以直接使用而不必編譯(這里的不用編譯實質上是有點爭議的,因為雖然Groovy腳本可以及時生效,但在其作為對象使用時還是使用Groovy本身提供的類庫生成了JVM所認識的字節碼,只不過我們看不到這個編譯后的文件而已,當然,為了運行效率的提高,你依然可以將其編譯成class文件,但前提是你寫好的*.groovy文件放在編譯目錄,而且一旦編譯,就不能實現我們的動態算法功能了,這里我們要討論的就是動態算法的融入,故不再贅述)。

先說下動態算法的實現吧,打破陳規,我們先不管Java如何調用Groovy,先看下Groovy的優勢,下面列出了我們常用的List、Map在Groovy中的使用

List: 
定義list:def list = [] 
list = [1,2,3,4,5] 

list操作: 
def list = [1,2,3,4,5] 
list[1]        //Result: 2 
list[-2]       //Result: 4 
list[1..3]     //Result: [2, 3, 4] 
list[1..<3]    //Result: [2, 3] 
list + [6,7]   //Result: [1, 2, 3, 4, 5, 6, 7] 
list - [4,5,6] //Result: [1, 2, 3] 
list < < 6      //Result: [1, 2, 3, 4, 5, 6] 
list << [6,7]  //Result: [1, 2, 3, 4, 5, 6, [6, 7]] 

list方法: 
[2,5].add(7)               //Result: true; list = [2, 5, 7] 
[2,5].add(1,9)             //list = [2, 7, 5] 
[2,5].add([7,9])           //Result: [2, 5, [7, 9]] 
[2, 5, [7, 9]].flatten()   //Result: [2, 5, 7, 9];克隆并解開下層list 
[2,5].get(1)               //Result: 5 
[2,5].size()               //Result: 2 
[2,5].isEmpty()            //Result: false 
[2,5].getAt(1)             //Result: 5 
[2,5,7].getAt(1..2)        //Result: [5, 7] 
[2,5,7].getAt(-1)          //Result: 7;get()不支持負數參數,getAt()支持 
[2,5,7].getAt([1,2])       //Result: [5, 7] 
[2,5,7].intersect([5,9,2]) //Result: [5, 2];交集 
[2,5,7].pop()              //Result: 7 
[2,5,7].plus([3,6])        //Result: [2, 5, 7, 3, 6] 
[2,5,7,2].minus(2)         //Result: [5, 7] 
[2,5,7].remove(1)          //Result: 5; list = [2, 7] 
[2,7,5].reverse()          //Result: [5, 7, 2] 
[2,7,5].sort()             //Result: [2, 5, 7] 

Map: 
定義Map:def map = [:] 
map = ['name':'Bruce', 'age':27] 

鍵被解釋成字符串: 
def x = 3 
def y = 5 
def map = [x:y, y:x] //Result: ["x":5, "y":3] 

如果要把值作為鍵,像下面這樣: 
def city = 'shanghai' 
map."${city}" = 'china' 
map.shanghai //Result: "china" 

map操作: 
def map = [3:56, 'name':'Bruce'] 
def a = 'name' 
map.name    //Result: "Bruce" 
map['name'] //Result: "Bruce" 
map[a]      //Result: "Bruce" 
map[3]      //Result: 56 
以下訪問是錯誤的,會拋出異常 
map[name] 
map.3 

map方法: 
def map = ['name':'Bruce', 'age':27] 
map.containsKey('name')   //Result: true 
map.get('name')           //Result: "Bruce" 
map.get('weight', '60kg') //Result: "60kg";會把key:value加進去 
map.getAt('age')          //Result: 27 
map.keySet()              //Result: [name, age, weight] 
map.put('height', '175')  //Result: ["name":"Bruce", "age":27, "weight":"60kg", "height":"175"] 
map.values().asList()     //Result: ["Bruce", 27, "60kg", "175"] 
map.size()                //Result: 4 

下列方法可以應用于范圍、List和Map(inject和reverseEach方法只適合List和范圍) 
each             void each(Closure clos)迭代集合中每個元素。 
find             List find(Closure clos)返回集合中第一個符合條件的元素。 
findAll          List findAll(Closure clos)返回集合中所有符合條件的元素。 
collect          List collect(Closure clos)返回計算后的列表。 
collect          List collect(Collection col, Closure clos)返回計算后的列表,同時把返回值保存到col集合里。 
any              boolean any(Closure clos)集合中有一個符合條件即返回true,否則返回false。 
every            boolean every(Closure clos)集合中所有都符合條件即返回true,否則返回false。 
findIndexOf      int findIndexOf(Closure clos)返回第一個符合條件元素在集合中的索引值(從0開始計算)。 
findLastIndexOf  int findLastIndexOf(Closure clos)返回最后一個符合條件元素在集合中的索引值(從0開始計算)。 
inject           Object inject(Object value, Closure clos)返回調用列表和參數的計算值。 
[1,2,3,4].inject(5) {x,y-> 
    x + y 
} 
//Result: 15 
reverseEach      void reverseEach(Closure clos)反響迭代集合中每個元素。 
[1,2,3,4].reverseEach {x-> 
    print x + '-' 
} 
//4-3-2-1- 
sort             List sort(Closure clos)按照閉包的返回條件排序。

可以看出,腳本語言該有的,我們Groovy基本都有實現,而且,我這邊現有系統的計算參數,就是以List

形式傳入的。補充一點,在Groovy里面,可以使用Java的所以類,但是我們要顯式的聲明引入,引入方式和Java的一樣,這樣的看來,我們基本可以在Groovy腳本內部實現sql、數據對接采集、計算規則定義等度量方法經常變更的部分了,是不是很心動?下面看看Groovy腳本寫出來是什么樣子的,基本語法這里就不說了。我們的算法至于是存數據庫還是存文件,這個也不討論了,最主要的是,算法該怎么定義?

如下,最簡單的計算:

def compute(list) {
    //TODO
    return list;
}

我們可以不用定義返回值類型、不用定義傳入值類型,他一樣能工作,如果你不習慣,也可以像下面這樣:

def compute(def list) {
    return list;
}
def List compute(def list) {
    //TODO
    return list;
}
def List compute(List list) {
    //TODO
    return list;
}
public List compute(List list) {
    //TODO
    return list;
}
public List< map <String,Object> > compute(List< <map <String,Object> > list) {
    //TODO
    return list;
}

很多朋友可能已經看出來了,沒錯,下面兩個就是Java的寫法,Groovy完全兼容,但是這里我們甚至可以把這個方法存在數據庫,在計算之前拿出來直接使用,如果某一天計算方法變了,我們只用更新數據庫字段值即可,是不是很方便呢?既然可以這樣,那么我們原有的連接池什么的公共接口是不是也可以在Groovy腳本里面使用了?答案是肯定的,我們只需顯式的引入相應包、相應類即可,不過要提的一點是:如果你想引入外部類庫等,且希望在腳本內部使用全局變量,你需要在你的方法外層套上class X{},不然解釋器會報錯,如下情況是不被允許的:

def name = "AVG";

def compute(def list) {
    return println(format(list));
}

需要改成:

class Avg{
    def name = "AVG";

    def compute(def list) {
        //TODO
        return list;
    }
}

同樣的,可以寫成標準Java類,如

public class Avg{
    private String name = "AVG";

    public List< map <String,Object> > compute(List< map <String,Object> > list) {
        //TODO
        return list;
    }
}

真正的整合環節到了,說了Groovy的好處,我們到底怎么樣整合到Java中呢?Java和groovy混合使用的方法有幾種呢?
實際上,我們有4種方式可以整合:
1、靜態編譯,在java工程中直接寫groovy的文件,然后可以在Groovy的文件中引用Java工程的類,這種方式能夠有效的利用groovy自身的語言特性,例如閉包; (這種方式上面已經提及,不適合我們目前需求)
2、通過groovyShell類直接執行腳本,例如:

Binding bind = new Binding();
bind.setVariable("str", "test");
GroovyShell shell = new GroovyShell(bind);
Object obj = shell.evaluate("return str");
System.out.println(obj);

3、通過groovyScriptEngine執行文件或者腳本,例如:

GroovyScriptEngine engine = new GroovyScriptEngine("groovy");
Object obj = engine.run("test.groovy","test");
System.out.println(obj);

?4、通過GroovyClassLoader來執行,例如:

String script="";//groovy script
ClassLoader parent = ClassLoader.getSystemClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
Class< ?> clazz = loader.parseClass(script);
GroovyObject clazzObj = (GroovyObject)clazz.newInstance();
System.out.println(clazzObj.invokeMethod("test", "str"));

需要注意的是,通過看groovy的創建類的地方,就能發現,每次執行的時候,都會新生成一個class文件,這樣就會導致JVM的perm區持續增長,進而導致FullGCc問題,解決辦法很簡單,就是腳本文件變化了之后才去創建文件,之前從緩存中獲取即可,緩存的實現可以采用簡單的Map或者使用之前文章提到的EhCache(同時可以設置緩存有效期,降低服務器壓力)。

在使用時,最好每次重新new classloader,因為如果腳本重新加載了,這時候就會有新老兩個class文件,如果通過一個classloader持有的話,這樣在GC掃描的時候,會認為老的類還在存活,導致回收不掉,所以每次new一個就能解決這個問題了。

注意CodeCache的設置大小(來自:http://hellojava.info/)
對于大量使用Groovy的應用,尤其是Groovy腳本還會經常更新的應用,由于這些Groovy腳本在執行了很多次后都會被JVM編譯為native進行優化,會占據一些CodeCache空間,而如果這樣的腳本很多的話,可能會導致CodeCache被用滿,而CodeCache一旦被用滿,JVM的Compiler就會被禁用,那性能下降的就不是一點點了。
Code Cache用滿一方面是因為空間可能不夠用,另一方面是Code Cache是不會回收的,所以會累積的越來越多(其實在不采用groovy這種動態更新/裝載class的情況下的話,是不會太多的),所以解法一可以是增大code cache的size,可通過在啟動參數上增加-XX:ReservedCodeCacheSize=256m(Oracle JVM Team那邊也是推薦把code cache調大的),二是啟用code cache的回收機制(關于Code Cache flushing的具體策略請參見此文),可通過在啟動參數上增加:-XX:+UseCodeCacheFlushing來啟用。

Java結合Groovy讓程序支持動態算法
文章《Java結合Groovy讓程序支持動態算法》二維碼
  • 微信打賞
  • 支付寶打賞

已有15條評論

  1. LAZADA

    MARK收藏。。。

    2016-08-23 00:06 回復
  2. 郵件營銷

    [感冒] MARK收藏。。。

    2016-03-11 04:18 回復
  3. 內涵笑話

    過來支持一下 值得收藏分享

    2015-10-01 16:49 回復
  4. 最勵志網

    路過,留個腳印,網站很棒!

    2015-08-17 16:43 回復
  5. osblog.net

    學習了,沒有用過

    2015-01-10 08:54 回復
  6. 周波

    寫的太好了,學習了
    有時間可以看下有關于 電信詐騙 的預防知識,希望對你有幫助 http://www.psdqw.com

    2014-12-16 19:25 回復
  7. 網絡兼職

    不玩Java 就會點簡單的php

    2014-12-16 15:08 回復
  8. 愛奇趣分享網

    寫的不錯

    2014-12-09 14:13 回復
  9. QQ云營銷

    看不懂,有點深奧,博主有空來我博客看看,指點一下

    2014-12-03 19:26 回復
  10. 悟空網賺(www.99shuobo.com)

    博客不錯。來學習了。也幫您添點人氣。有空來我這邊逛逛喔。——悟空網賺

    2014-11-28 20:56 回復
  11. 香港云主機

    你好!我這里有個獨立ip主機3.8折的廣告圖推薦給你如何?如果可以的話,請加我qq:2954243953

    2014-11-25 10:50 回復
  12. 云淡天高

    樓主是做java開發的是吧,恩總結的內容很有價值,我是做網絡優化和營銷策劃的,跟你專業雖然不太相同,但是有很多相似的地方,我的博客也是更新最新的動態和自己的經驗總結。云天博客:http://www.yuntianseo.com/(專注于網絡營銷推廣經驗交流),當然如果對網絡營銷剛興趣的可以加我的QQ:1248158978

    2014-11-23 10:49 回復

(必填)

(必填)

(可選)

黑龙江22选5开奖