最近在公司寫實時核對系統,其中在核對環節用到了groovy做動態腳本的解析、編譯及運算,本文介紹下如何使用能保證線程安全。
引入groovy的相關依賴
<code><dependency>
<groupid>org.codehaus.groovy/<groupid>
<artifactid>groovy/<artifactid>
<version>2.1.6/<version>
/<dependency>
<dependency>
<groupid>org.codehaus.groovy/<groupid>
<artifactid>groovy-jsr223/<artifactid>
<version>2.1.6/<version>
/<dependency>/<code>
因為腳本的編譯會花費比較長的時間,不可能在每次請求到達時在編譯,所以可以在系統啟動的時候先把腳本編譯後放入到本地緩存,這裡可以使用Map,也可以使用一些其他的框架,比如guava、caffeine都是可以的,後者現在已經是springboot2.x系列首選的本地緩存框架了。
我選擇的是使用編譯好的Script放在Map裡。
在使用的時候有一點需要特別說明,在高併發下會涉及到線程安全的問題。
要保證線程安全,這裡有兩種做法。
第一種如下:
<code>String expression = = "if (obj.typeId==1) return '1' else return '111111';";
GroovyShell shell = new GroovyShell();
Script/>Binding binding = new Binding();
JSONObject json = new JSONObject();
json.put("typeId", 1);
binding.setVariable("obj", json);
script.setBinding(binding);
Object evaluate =>
這裡相當於每次請求進來都會new一個新的對象,線程獨享所以線程安全。
第二種方案關鍵代碼如下:
<code>public String equalsCheck() {
Script/>
Binding binding = new Binding();
binding.setProperty("left", checkContext.getReqJson());
binding.setProperty("right", checkContext.getResJson());
/> return InvokerHelper.createScript(script.getClass(), binding).run().toString();
}/<code>
系統啟動時先進行預編譯,在執行上述方法時先獲得在執行。
這種做法是線程安全的,詳情如下代碼:
代碼分析可以看出,方法執行時script的初始值為空,這裡的傳參class不為空且為Script的class對象,故會都到else的邏輯裡。
這裡邏輯是根據參數class對象生成一個新的對象,並對變量script進行復制。
如此則每個線程都會有一個單獨的Script對象,獨享的,binding對象也是該Script對象對象的私有變量,也是線程安全的。
閱讀更多 java知識之路 的文章