目錄
一、概述
Groovy is a multi-faceted language for the Java platform.
Apache Groovy是一種強大的、可選的類型化和動態語言,具有靜態類型和靜態編譯功能,用於Java平臺,目的在於通過簡潔、熟悉和易於學習的語法提高開發人員的工作效率。它可以與任何Java程序順利集成,並立即向您的應用程序提供強大的功能,包括腳本編寫功能、特定於域的語言編寫、運行時和編譯時元編程以及函數式編程。
Groovy是基於java虛擬機的,執行文件可以是簡單的腳本片段,也可以是一個完整的groovy class,對於java程序員來說,學習成本低,可以完全用java語法編寫。下面總結下我的學習筆記,java整合Groovy的四種調用方式。
二、pom文件
添加groovy-all jar包,以及groovy-sanbox提供groovy安全的沙盒環境
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.16</version>
</dependency>
<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>groovy-sandbox</artifactId>
<version>1.7</version>
</dependency>
三、ScriptEngineManager
groovy遵循JSR 223標準,可以使用jdk的標準接口ScriptEngineManager調用。
@Test
public void testScriptEngine() throws ScriptException, NoSuchMethodException {
ScriptEngineManager factory = new ScriptEngineManager();
// 每次生成一個engine實例
ScriptEngine engine = factory.getEngineByName("groovy");
System.out.println(engine.toString());
// javax.script.Bindings
Bindings binding = engine.createBindings();
binding.put("date", new Date());
// 如果script文本來自文件,請首先獲取文件內容
engine.eval("def getTime(){return date.getTime();}", binding);
engine.eval("def sayHello(name,age){return 'Hello,I am ' + name + ',age' + age;}");
Long time = (Long) ((Invocable) engine).invokeFunction("getTime", null);
System.out.println(time);
String message = (String) ((Invocable) engine).invokeFunction("sayHello", "zhangsan", 12);
System.out.println(message);
}
四、GroovyShell
直接使用GroovyShell,執行groovy腳本片段,GroovyShell每一次執行時代碼時會動態將代碼編譯成java class,然後生成java對象在java虛擬機上執行,所以如果使用GroovyShell會造成class太多,性能較差。
@Test
public void testGroovyShell() {
final String script = "Runtime.getRuntime().availableProcessors()";
Binding intBinding = new Binding();
GroovyShell shell = new GroovyShell(intBinding);
final Object eval = shell.evaluate(script);
System.out.println(eval);
}
五、GroovyClassLoader
groovy官方提供GroovyClassLoader從文件,url或字符串中加載解析Groovy class
@Test
public void testGroovyClassLoader() throws IllegalAccessException, InstantiationException {
GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
String hello = "package com.szwn.util\n" + "\n" + "class GroovyHello {\n" + " String sayHello(String name) {\n"
+ " print 'GroovyHello call '\n" + " name\n" + " }\n" + "}";
Class aClass = groovyClassLoader.parseClass(hello);
GroovyObject object = (GroovyObject) aClass.newInstance();
Object o = object.invokeMethod("sayHello", "zhangsan");
System.out.println(o.toString());
}
六、GroovyScriptEngine
GroovyScriptEngine可以從url(文件夾,遠程協議地址,jar包)等位置動態加裝resource(script或則Class),同時對
編譯後的class字節碼進行了緩存,當文件內容更新或者文件依賴的類更新時,會自動更新緩存。
@Test
public void testGroovyScriptEngine() throws IOException, ResourceException, groovy.util.ScriptException {
String url = "D:\\groovy\\util";
GroovyScriptEngine engine = new GroovyScriptEngine(url);
for (int i = 0; i < 5; i++) {
Binding binding = new Binding();
binding.setVariable("index", i);
// 每一次執行獲取緩存Class,創建新的Script對象
Object run = engine.run("GroovyHello.groovy", binding);
System.out.println(run);
}
}
GroovyHello.groovy代碼如下
String sayHello(name) {
print "GroovyHello to 中文"
name
}
sayHello(name)
七、SecureASTCustomizer
Groovy會自動引入java.util,java.lang包,方便用戶調用,但同時也增加了系統的風險。爲了防止用戶調用System.exit或Runtime等方法導致系統宕機,以及自定義的groovy片段代碼執行死循環或調用資源超時等問題,Groovy提供了SecureASTCustomizer安全管理者和SandboxTransformer沙盒環境。
@Test
public void testAST() {
final String script = "import com.alibaba.fastjson.JSONObject;JSONObject object = new JSONObject()";
// 創建SecureASTCustomizer
final SecureASTCustomizer secure = new SecureASTCustomizer();
// 禁止使用閉包
secure.setClosuresAllowed(true);
List<Integer> tokensBlacklist = new ArrayList<>();
// 添加關鍵字黑名單 while和goto
tokensBlacklist.add(Types.KEYWORD_WHILE);
tokensBlacklist.add(Types.KEYWORD_GOTO);
secure.setTokensBlacklist(tokensBlacklist);
// 設置直接導入檢查
secure.setIndirectImportCheckEnabled(true);
// 添加導入黑名單,用戶不能導入JSONObject
List<String> list = new ArrayList<>();
list.add("com.alibaba.fastjson.JSONObject");
secure.setImportsBlacklist(list);
// statement 黑名單,不能使用while循環塊
List<Class<? extends Statement>> statementBlacklist = new ArrayList<>();
statementBlacklist.add(WhileStatement.class);
secure.setStatementsBlacklist(statementBlacklist);
// 自定義CompilerConfiguration,設置AST
final CompilerConfiguration config = new CompilerConfiguration();
config.addCompilationCustomizers(secure);
Binding intBinding = new Binding();
GroovyShell shell = new GroovyShell(intBinding, config);
final Object eval = shell.evaluate(script);
System.out.println(eval);
}
執行結果
八、SandboxTransformer
用戶調用System.exit或調用Runtime的所有靜態方法都會拋出SecurityException
@Test
public void testGroovySandbox() {
// 自定義配置
CompilerConfiguration config = new CompilerConfiguration();
// 添加線程中斷攔截器,可攔截循環體(for,while)、方法和閉包的首指令
config.addCompilationCustomizers(new ASTTransformationCustomizer(ThreadInterrupt.class));
// 添加線程中斷攔截器,可中斷超時線程,當前定義超時時間爲3s
Map<String, Object> timeoutArgs = ImmutableMap.of("value", 3);
config.addCompilationCustomizers(new ASTTransformationCustomizer(timeoutArgs, TimedInterrupt.class));
// 沙盒環境
config.addCompilationCustomizers(new SandboxTransformer());
GroovyShell sh = new GroovyShell(config);
// 註冊至當前線程
new NoSystemExitSandbox().register();
new NoRunTimeSandbox().register();
// 確保在每次更新緩存Class<Script>對象時候,採用不同的groovyClassLoader
// Script groovyScript = sh.parse("System.exit(1)");
// Script groovyScript = sh.parse("Runtime.getRuntime().availableProcessors()");
Script groovyScript = sh.parse("System.exit(1)");
Object run = groovyScript.run();
System.out.println(run);
}
class NoSystemExitSandbox extends GroovyInterceptor {
@Override
public Object onStaticCall(GroovyInterceptor.Invoker invoker, Class receiver, String method, Object... args) throws Throwable {
if (receiver == System.class && method.equals("exit")) {
throw new SecurityException("No call on System.exit() please");
}
return super.onStaticCall(invoker, receiver, method, args);
}
}
class NoRunTimeSandbox extends GroovyInterceptor {
@Override
public Object onStaticCall(GroovyInterceptor.Invoker invoker, Class receiver, String method, Object... args) throws Throwable {
if (receiver == Runtime.class) {
throw new SecurityException("No call on RunTime please");
}
return super.onStaticCall(invoker, receiver, method, args);
}
}
九、DSL(Json轉換)
Groovy支持元數據編程,會將所有groovy代碼編譯成java對象,如果是groovy代碼片段則會動態編譯成groovy.lang.Script對象,如果是class類,則會動態編譯類實現groovy.lang.GroovyObject接口。編譯後對象方法的調用,都會調用GroovyObject接口的invokeMethod方法,下面是我重寫invokeMethod方法實現json自動轉換的dsl代碼。
def invokeMethod(String name, Object args) {
Object[] objArgs = (Object[]) args
JSONObject currentJson = (JSONObject) this.getProperty("current_json")
if (objArgs.size() == 1) {
Object arg = objArgs[0]
if (arg instanceof Closure) {
JSONObject newJson = new JSONObject()
currentJson.put(name, newJson)
this.setProperty("current_json", newJson)
arg.delegate = this
arg.resolveStrategy = Closure.DELEGATE_ONLY
arg.run()
this.setProperty("current_json", currentJson)
} else {
currentJson.put(name, arg)
}
} else if (objArgs.size() == 2) {
Object it = objArgs[0]
Closure single = (Closure) objArgs[1]
JSONObject newJson = new JSONObject()
currentJson.put(name, newJson)
this.setProperty("current_json", newJson)
single.setProperty("it", it)
single.call(it)
this.setProperty("current_json", currentJson)
} else if (objArgs.size() == 3) {
Closure eachCl = (Closure) objArgs[2]
int rule = (int) objArgs[1]
if (rule == 2) {
JSONArray array = new JSONArray()
currentJson.put(name, array)
objArgs[0].each {
JSONObject newJson = new JSONObject()
array.add(newJson)
eachCl.setProperty("it", it)
this.setProperty("current_json", newJson)
eachCl.call(it)
}
} else if (rule == 1) {
JSONObject newJson = new JSONObject()
currentJson.put(name, newJson)
objArgs[0].each {
eachCl.setProperty("it", it)
this.setProperty("current_json", newJson)
eachCl.call(it)
}
} else if (rule == 3) {
objArgs[0].each {
eachCl.setProperty("it", it)
eachCl.call(it)
}
}
this.setProperty("current_json", currentJson)
}
}