目錄
一、場景描述
二、javascript語法引擎
JavaScriptEngine
getEngineByName
eval
NashornScriptEngine
三、Groovy語法引擎
一.使用GroovyClassLoader
二、原理?
三、調用groovy腳本實現(xiàn)方式
1.使用GroovyClassLoader
2.使用ScriptEngine
3.使用GroovyShell
四、性能優(yōu)化
?五、解決方案
四、項目實戰(zhàn)?
一、概述
二、項目描述
三、設計Groovy模版表,存儲Groovy腳本?
四、用戶事件表
五、Spring Bean
六、單元測試?
學習
前言
互聯(lián)網(wǎng)時代隨著業(yè)務的飛速發(fā)展,不僅產(chǎn)品迭代、更新的速度越來越快,個性化需求也是越來越多。如何快速的滿足各種業(yè)務的個性化需求是我們要重點思考的問題。我們開發(fā)的系統(tǒng)如何才能做到熱部署,不重啟服務就能適應各種規(guī)則變化呢?實現(xiàn)業(yè)務和規(guī)則的解耦 和 系統(tǒng)高可用性。
好了,Java的ScriptEngine腳本引擎給了我們一個選擇,它支持代碼動態(tài)執(zhí)行,代碼修改后不需要重啟JVM進程,就可以使用解析或編譯方式執(zhí)行,非常方便,在一些動態(tài)業(yè)務規(guī)則、熱更新、熱修復等場景中會非常方便。
一、場景描述
在互聯(lián)網(wǎng)項目中,我們?yōu)榱艘鞒3O計一些活動來吸引用戶。而活動的規(guī)則呢,往往五花八門?;顒雍鸵?guī)則耦合太緊會導致系統(tǒng)很臃腫,難以維護,規(guī)則的變動往往需要重啟服務器。我們思考是否可以將規(guī)則設計成一個黑盒子,我們傳遞相應的輸入,期望得到相應的輸出結果。
????????????????????????
活動只需要知道規(guī)則腳本的位置,執(zhí)行規(guī)則腳本而不需要知道它是如何執(zhí)行的。規(guī)則腳本可以存儲在數(shù)據(jù)庫,磁盤文件等地方。
下面我們先介紹下各種引擎。
二、javascript語法引擎
ScriptEngineManager
?為?ScriptEngine
?類實現(xiàn)一個發(fā)現(xiàn)和實例化機制,還維護一個鍵/值對集合來存儲所有 Manager 創(chuàng)建的引擎所共享的狀態(tài)。此類使用服務提供者機制枚舉所有的?ScriptEngineFactory
?實現(xiàn)。ScriptEngineManager
?提供了一個方法,可以返回一個所有工廠實現(xiàn)和基于語言名稱、文件擴展名和 mime 類型查找工廠的實用方法所組成的數(shù)組。
鍵/值對的?Bindings
(即由管理器維護的 "Global Scope")對于?ScriptEngineManager
?創(chuàng)建的所有?ScriptEngine
?實例都是可用的。Bindings
?中的值通常公開于所有腳本中。
JavaScriptEngine
public class JavaScriptTest {
public static void main(String[] args) throws Exception {
String js = " function add (a, b) { " +
" var sum = a + b; " +
//js調用java類
" java.lang.System.out.println(\"Script sum=\" + sum); " +
" return java.lang.Integer.valueOf(sum); " +
"}";
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
engine.eval(js);
Invocable jsInvoke = (Invocable) engine;
Object result = jsInvoke.invokeFunction("add", new Object[]{1, 2});
}
}
ScriptEngine (Java 2 Platform SE 6)
getEngineByName
public ScriptEngine getEngineByName(String?shortName)查找并創(chuàng)建一個給定名稱的?
ScriptEngine
。該算法首先查找一個?ScriptEngineFactory
,該?ScriptEngineFactory
?已經(jīng)針對給定名稱使用?registerEngineName
?方法注冊為處理程序。
如果沒有找到這樣的 ScriptEngineFactory,則搜索構造方法存儲的?ScriptEngineFactory
?實例數(shù)組,以獲得具有指定名稱的 ScriptEngineFactory。如果通過這兩種方法之一找到了一個?ScriptEngineFactory
,則用它來創(chuàng)建?ScriptEngine
?實例。參數(shù):
shortName
?-?ScriptEngine
?實現(xiàn)的短名稱,由其?ScriptEngineFactory
?的?getNames
?方法返回。返回:
搜索到的工廠所創(chuàng)建的?
ScriptEngine
。如果沒有找到這樣的工廠,則返回 null。ScriptEngineManager
?將它自己的?globalScope
?Bindings
?設置為新建?ScriptEngine
?的?GLOBAL_SCOPE
?Bindings
。拋出:
NullPointerException
?- 如果 shortName 為 null
eval
Object eval(String?script) throws ScriptException執(zhí)行指定的腳本。使用?
ScriptEngine
?的默認?ScriptContext
。參數(shù):
script
?- 要執(zhí)行的腳本語言源。返回:
執(zhí)行腳本所返回的值。
拋出:
ScriptException
?- 如果腳本發(fā)生錯誤。
NullPointerException
?- 如果參數(shù)為 null。
NashornScriptEngine
從 JDK 1.8 開始,Nashorn取代Rhino(JDK 1.6, JDK1.7) 成為 Java 的嵌入式 JavaScript 引擎。Nashorn 完全支持 ECMAScript 5.1 規(guī)范以及一些擴展。它使用基于 JSR 292 的新語言特性,其中包含在 JDK 7 中引入的 invokedynamic,將 JavaScript 編譯成 Java 字節(jié)碼。
與先前的 Rhino 實現(xiàn)相比,這帶來了 2 到 10倍的性能提升
public static void main(String[] args) throws Exception {
String js = " function add (a, b) { " +
" var sum = a + b; " +
// js調用java類
" java.lang.System.out.println(\"Script sum=\" + sum); " +
" return java.lang.Integer.valueOf(sum); " +
"}";
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");
engine.eval(js);
Invocable jsInvoke = (Invocable) engine;
Object result = jsInvoke.invokeFunction("add", new Object[]{1, 2});
}
三、Groovy語法引擎
一.使用GroovyClassLoader
用 Groovy 的 GroovyClassLoader ,它會動態(tài)地加載一個腳本并執(zhí)行它。GroovyClassLoader是一個Groovy定制的類裝載器,負責解析加載Java類中用到的Groovy類
Groovy是在Java虛擬機上實現(xiàn)的動態(tài)語言,提供了動態(tài)將java代碼編譯為Java Class對象的功能。需要添加依賴包
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>3.0.16</version>
<type>pom</type>
</dependency>
代碼如下(示例1):
public class GroovyTest {
public static void main(String[] args) throws Exception {
GroovyClassLoader loader = new GroovyClassLoader();
// java代碼
String java = " " +
" public class Test { " +
" public int add(double a, double b) { " +
" double sum = a + b; " +
" System.out.println(\"Script sum=\" + sum); " +
" return sum.intValue(); " +
" } " +
" } ";
Class scriptClass = loader.parseClass(java);
GroovyObject scriptInstance = (GroovyObject) scriptClass.getDeclaredConstructor().newInstance();
Object result = scriptInstance.invokeMethod("add", new Object[]{1, 2});
System.out.println("Groovy result=" + result);
}
}
?代碼如下(示例2):
public class GroovyTest {
public static void main(String[] args) throws Exception {
String groovy = " def call(int a,int b) { " +
" return a + b " +
"}";
GroovyClassLoader loader = new GroovyClassLoader();
Class scriptClass = loader.parseClass(groovy);
GroovyObject scriptInstance = (GroovyObject) scriptClass.newInstance();
result = scriptInstance.invokeMethod("call",new Object[] {1,2});
System.out.println("Groovy result=" + result);
}
}
如果一切都需要在1行,那么你在return語句之前就錯過了一個分號。 如:
?boolean engineTest = false; if (!engineTest) { engineTest = true}; return engineTest;?
二、原理?
GroovyClassLoader是一個定制的類裝載器,在代碼執(zhí)行時動態(tài)加載groovy腳本為java對象。
大家都知道classloader的雙親委派,我們先來分析一下這個GroovyClassloader,看看它的祖先分別是啥:
使用idea 創(chuàng)建一個 Groovy項目
運行結果:
groovy.lang.GroovyClassLoader$InnerLoader@432038ec
groovy.lang.GroovyClassLoader@51891008
org.codehaus.groovy.tools.RootLoader@4d405ef7
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@ab7395e進程已結束,退出代碼0
從而得出Groovy的ClassLoader體系:
Bootstrap ClassLoader ↑ sun.misc.Launcher.ExtClassLoader // 即Extension ClassLoader ↑ sun.misc.Launcher.AppClassLoader // 即System ClassLoader ↑ org.codehaus.groovy.tools.RootLoader // 以下為User Custom ClassLoader ↑ groovy.lang.GroovyClassLoader ↑ groovy.lang.GroovyClassLoader.InnerLoader
三、調用groovy腳本實現(xiàn)方式
1.使用GroovyClassLoader
private static void invoke(String scriptText, String function, Object... objects) throws Exception {
GroovyClassLoader classLoader = new GroovyClassLoader();
Class groovyClass = classLoader.parseClass(scriptText);
try {
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
groovyObject.invokeMethod(function,objects);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
2.使用ScriptEngine
private static final GroovyScriptEngineFactory scriptEngineFactory = new GroovyScriptEngineFactory();
private static <T> T invoke(String script, String function, Object... objects) throws Exception {
ScriptEngine scriptEngine = scriptEngineFactory.getScriptEngine();
scriptEngine.eval(script);
return (T) ((Invocable) scriptEngine).invokeFunction(function, objects);
}
3.使用GroovyShell
private static GroovyShell groovyShell = new GroovyShell();
private static <T> T invoke(String scriptText, String function, Object... objects) throws Exception {
Script script= groovyShell.parse(scriptText);
return (T) InvokerHelper.invokeMethod(script, function, objects);
}
四、性能優(yōu)化
項目在測試時發(fā)現(xiàn),加載的類隨著程序運行越來越多,而且垃圾收集也非常頻繁。
groovy腳本執(zhí)行的過程
GroovyClassLoader classLoader = new GroovyClassLoader();
Class groovyClass = classLoader.parseClass(scriptText);
try {
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
groovyObject.invokeMethod(function,objects);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
查看GroovyClassLoader.parseClass方法,發(fā)現(xiàn)如下代碼:?
public Class parseClass(String text) throws CompilationFailedException {
return this.parseClass(text, "script" +
System.currentTimeMillis()
+ Math.abs(text.hashCode()) + ".groovy");
}
public Class parseClass(final String text, final String fileName) throws CompilationFailedException {
GroovyCodeSource gcs = (GroovyCodeSource)AccessController.doPrivileged(new PrivilegedAction<GroovyCodeSource>() {
public GroovyCodeSource run() {
return new GroovyCodeSource(text, fileName, "/groovy/script");
}
});
gcs.setCachable(false);
return this.parseClass(gcs);
}
protected ClassCollector createCollector(CompilationUnit unit, SourceUnit su) {
InnerLoader loader = (InnerLoader)AccessController.doPrivileged(new PrivilegedAction<InnerLoader>() {
public InnerLoader run() {
return new InnerLoader(GroovyClassLoader.this);
}
});
return new ClassCollector(loader, unit, su);
}
這兩處代碼的意思是:
groovy每執(zhí)行一次腳本,都會生成一個腳本的class對象,這個class對象的名字由 “script” + System.currentTimeMillis() +
Math.abs(text.hashCode()組成,對于問題1:每次執(zhí)行同一個StrategyLogicUnit時,產(chǎn)生的class都不同,每次執(zhí)行規(guī)則腳本都會產(chǎn)生一個新的class。
接著看問題2? InnerLoader部分:
groovy每執(zhí)行一次腳本都會new一個InnerLoader去加載這個對象,而對于問題2,我們可以推測:InnerLoader和腳本對象都無法在fullGC的時候被回收,因此運行一段時間后將PERM占滿,一直觸發(fā)fullGC。
?五、解決方案
把每次腳本生成的對象緩存起來
private final ConcurrentHashMap<Integer, GroovyObject> groovyMap = new ConcurrentHashMap();
private final ReentrantLock lock = new ReentrantLock();
public Object invoke(String scriptId) {
GroovyObject scriptInstance = groovyMap.get(scriptId);
if (scriptInstance == null) {
lock.lock();
try {
scriptInstance = groovyMap.get(scriptId);
if (scriptInstance == null ) {
GroovyClassLoader loader = new GroovyClassLoader();
Class scriptClass = loader.parseClass(script);
scriptInstance = (GroovyObject) scriptClass.getDeclaredConstructor().newInstance();
groovyMap.put(scriptId, scriptInstance);
}
} finally {
lock.unlock();
}
}
Object result = scriptInstance.invokeMethod("match", new Object[]{map});
return result;
}
四、項目實戰(zhàn)?
一、概述
Groovy?is a multi-faceted language for the Java platform.
Apache?Groovy是一種強大的、可選的類型化和動態(tài)語言,具有靜態(tài)類型和靜態(tài)編譯功能,用于Java平臺,目的在于通過簡潔、熟悉和易于學習的語法提高開發(fā)人員的工作效率。它可以與任何Java程序順利集成,并立即向您的應用程序提供強大的功能,包括腳本編寫功能、特定于域的語言編寫、運行時和編譯時元編程以及函數(shù)式編程。
Groovy是基于java虛擬機的,執(zhí)行文件可以是簡單的腳本片段,也可以是一個完整的groovy class,對于java程序員來說,學習成本低,可以完全用java語法編寫。
二、項目描述
我們要 設計這樣一個系統(tǒng),根據(jù)用戶的行為,贈送用戶福利。比如閱讀書籍時長超過300S,我們會贈送用戶書券;用戶注冊為正式用戶,我們贈送用戶積分等操作。諸如此類多條件或并的復雜場景。
三、設計Groovy模版表,存儲Groovy腳本?
我們先設計腳本模版表groovy
字段名稱 | 字段類型 | 描述 |
---|---|---|
id | BIGINT(20) | 主鍵 |
groovy_code | VARCHAR(32) | Groovy模版編碼 |
groovy_name | VARCHAR(32) | Groovy模版名稱 |
content | TEXT | 模版內容 |
status | TINYINT(4) | 狀態(tài) ?1:啟用 ?2:停用 |
update_time | DATETIME | 修改時間 |
updater | VARCHAR(32) | 最后修改人 |
version | INT(10) | 版本號 |
數(shù)據(jù)儲存如圖:
四、用戶事件表
五、Spring Bean
不能使用@Autowired(autowired是在Spring啟動后注入的,此時還未加載groovy代碼,故無法注入)
建議實現(xiàn)ApplicationContextAware接口的工具(組件)來獲取Spring Bean
調用Spring Bean的腳本
import com.xinwu.shushan.core.common.ApplicationContextHelper;
import com.xinwu.shushan.launch.infra.cache.AdClickCache;
public class Groovy {
public Boolean match(Map<String, Object> map) {
AdClickCache adClickCache = ApplicationContextHelper.getBean(AdClickCache.class);
//閾值
Integer threshold = (Integer) map.get("threshold");
//用戶ID
Integer userId = (Integer) map.get("userId");
//日期
Date date = (Date) map.get("date");
//產(chǎn)品線
Integer productType = (Integer) map.get("productType");
//廣告點擊數(shù)
int adClickCount = adClickCache.get(date, productType, userId);
return adClickCount >= threshold;
}
}
六、單元測試?
import com.google.common.collect.Maps;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import java.util.Date;
import java.util.Map;
public class AdClickGroovyTest {
public static void main(String[] args) throws Exception {
String script = "" +
"import com.xinwu.shushan.core.common.ApplicationContextHelper;\n" +
"import com.xinwu.shushan.launch.infra.cache.AdClickCache;\n" +
" \n" +
"public class Groovy {\n" +
" public Boolean match(Map<String, Object> map) {\n" +
" AdClickCache adClickCache = ApplicationContextHelper.getBean(AdClickCache.class);\n" +
" \n" +
" //閾值\n" +
" Integer threshold = (Integer) map.get(\"threshold\");\n" +
" //用戶ID\n" +
" Integer userId = (Integer) map.get(\"userId\");\n" +
" //日期\n" +
" Date date = (Date) map.get(\"date\");\n" +
" //產(chǎn)品線\n" +
" Integer productType = (Integer) map.get(\"productType\");\n" +
" //廣告點擊數(shù)\n" +
" int adClickCount = adClickCache.get(date, productType, userId);\n" +
" \n" +
" return adClickCount >= threshold;\n" +
" }\n" +
"}";
GroovyClassLoader loader = new GroovyClassLoader();
Class scriptClass = loader.parseClass(script);
GroovyObject scriptInstance = (GroovyObject) scriptClass.newInstance();
Map<String, Object> map = Maps.newHashMap();
map.put("threshold", 2);
map.put("userId", 10001);
map.put("date", new Date());
map.put("productType", 1);
Object result = scriptInstance.invokeMethod("match", new Object[]{map});
System.out.println("Groovy result=" + result);
}
}
如果你想在Java里使用一個接口,但是接口的實現(xiàn)在Groovy腳本中,可以這樣?文章來源:http://www.zghlxwxcb.cn/news/detail-722867.html
public class TestGroovy {
//@Test
public static void main(String[] args) throws Exception {
String groovy = "public class Groovy implements com.xinwu.shushan.launch.groovy.GroovyInterface {\n" +
" Map<Object, Integer> match(Map<String, Object> map) {\n" +
"\n" +
" Integer threshold = (Integer) map.get(\"threshold\");\n" +
" Integer userId = (Integer) map.get(\"userId\");\n" +
" Date date = (Date) map.get(\"date\");\n" +
"\n" +
"\n" +
" return null;\n" +
" }\n" +
"}";
GroovyClassLoader loader = new GroovyClassLoader();
Class scriptClass = loader.parseClass(groovy);
Object aScript = scriptClass.newInstance();
Map<String, Object> map = Maps.newHashMap();
map.put("threshold", 1);
map.put("userId", 1);
map.put("date", new Date());
GroovyInterface groovyInterface = (GroovyInterface) aScript;
Object result = groovyInterface.match(map);
System.out.println("GroovyInterface result=" + result);
}
}
學習
復雜多變場景下的Groovy腳本引擎實戰(zhàn)文章來源地址http://www.zghlxwxcb.cn/news/detail-722867.html
到了這里,關于java腳本引擎Groovy實戰(zhàn)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!