參照:https://juejin.cn/post/6954350461818421278#heading-1
1. 編寫 Groovy 邏輯的兩種風格
- 腳本(不定義和.groovy文件同名的class)
- 類(定義class,所有代碼都在class里)
在 .groovy
文件內,可以不聲明任何類而直接在文件頂級層次編寫代碼邏輯 (筆者剛才就是這樣做的)。不過這樣的話,就不能在文件的頂級層次再聲明一個和文件同名的類,否則編譯器會給出 there is a synthetic class generated for script code
的錯誤。從編譯角度來看這可以理解,因為 .groovy
文件被編譯成 .class
文件並執行時,編譯器實際上會為其生成一個合成類,而正是這一步導致了沖突發生:我們剛定義的類名和它重復了。
.groovy
文件內部出現了和文件同名的類,則意味着這個
.groovy
文件會被視作是一段 "用 Groovy 方言編寫的 Java 代碼",一般它也就不再作為腳本使用,而是變成一個 "普通的類" ( IDEA 稱它是一個 Groovy Class) 。這么做的一個直接后果是,我們不能夠在文件的頂級層次直接編寫代碼邏輯。
2. Java集成groovy之GroovyShell、GroovyScriptEngine、GroovyClassLoader
https://www.cnblogs.com/jsersudo/p/10178407.html
- GroovyClassLoader
用 Groovy 的 GroovyClassLoader ,動態地加載一個腳本並執行它的行為。GroovyClassLoader是一個定制的類裝載器,
負責解釋加載Java類中用到的Groovy類。
- GroovyShell
GroovyShell允許在Java類中(甚至Groovy類)求任意Groovy表達式的值。您可使用Binding對象輸入參數給表達式,
並最終通過GroovyShell返回Groovy表達式的計算結果。
- GroovyScriptEngine
GroovyShell多用於推求對立的腳本或表達式,如果換成相互關聯的多個腳本,使用GroovyScriptEngine會更好些。
GroovyScriptEngine從您指定的位置(文件系統,URL,數據庫,等等)加載Groovy腳本,並且隨着腳本變化而重新加載它們。
如同GroovyShell一樣,GroovyScriptEngine也允許您傳入參數值,並能返回腳本的值。
1 package groovy; 2 3 import groovy.lang.*; 4 import groovy.util.GroovyScriptEngine; 5 import groovy.util.ResourceException; 6 import groovy.util.ScriptException; 7 8 import java.io.File; 9 import java.io.IOException; 10 11 public class GroovyIntoJavaDemo1 { 12 //測試次數 13 private static final int num = 10000; 14 15 public static void main(String[] args) throws IOException, ResourceException, ScriptException { 16 17 /* 18 GroovyClassLoader 19 */ 20 GroovyClassLoader loader = new GroovyClassLoader(); 21 Class aClass = loader.parseClass(new File("src/main/java/groovy/CycleDemo.groovy")); 22 try { 23 GroovyObject instance = (GroovyObject) aClass.newInstance(); 24 instance.invokeMethod("cycle", new Object[] {"GroovyClassLoader", num}); 25 26 } catch (InstantiationException e) { 27 e.printStackTrace(); 28 } catch (IllegalAccessException e) { 29 e.printStackTrace(); 30 } 31 32 33 /* 34 GroovyShell 35 */ 36 new GroovyShell().parse( new File( "src/main/java/groovy/CycleDemo.groovy" ) ) 37 .invokeMethod("cycle", new Object[] {"GroovyShell", num}); 38 39 /* 40 GroovyShell,Binding 41 */ 42 Binding binding = new Binding(); 43 // 傳參數 44 binding.setVariable("scene", "GroovyShell_Binding"); 45 binding.setVariable("number", num); 46 47 GroovyShell groovyShell = new GroovyShell(binding); 48 Script script = groovyShell.parse(new File( "src/main/java/groovy/CycleDemo.groovy" )); 49 binding.setVariable("cycleDemo", script); // 傳腳本實例 50 51 script.evaluate("cycleDemo.cycle(scene, number)"); // in_cycle, number=10000 52 53 groovyShell.evaluate(new File( "src/main/java/groovy/CycleDemo.groovy" )); // out_of_cycle,無法調用到方法 54 55 56 /* 57 GroovyScriptEngine 58 */ 59 Class script1 = new GroovyScriptEngine("src/main/java/groovy/") 60 .loadScriptByName("CycleDemo.groovy"); 61 try { 62 Script instance =(Script) script1.newInstance(); 63 instance.invokeMethod ("cycle",new Object[]{"GroovyScriptEngine", num}); 64 } catch (InstantiationException e) { 65 e.printStackTrace(); 66 } catch (IllegalAccessException e) { 67 e.printStackTrace(); 68 } 69 } 70 }
執行結果:
1 [GroovyClassLoader]in_cycle, number=10000 2 [GroovyShell]in_cycle, number=10000 3 [GroovyShell_Binding]in_cycle, number=10000 4 out_of_cycle 5 [GroovyScriptEngine]in_cycle, number=10000
2.1 evaluate
Script:
Object evaluate(String expression) // 參數為groovy表達式
GroovyShell:
public Object evaluate(final String scriptText) // 參數為groovy腳本
https://blog.csdn.net/jiangtao_st/article/details/19496989
1 package groovy; 2 3 import groovy.lang.Binding; 4 import groovy.lang.GroovyShell; 5 import groovy.lang.Script; 6 7 public class TestScript { 8 public static void main(String[] args) { 9 10 GroovyShell groovyShell = new GroovyShell(); 11 /** 12 * 腳本為 13 def customConcat(def str1, def str2) { 14 str1.concat(str2) 15 } 16 */ 17 Script scrpt = groovyShell.parse("\n" + 18 "def customConcat(def str1, def str2) {\n" + 19 "str1.concat(str2)\n" + 20 "}"); 21 22 Binding binding = new Binding(); 23 binding.setVariable("str1", "value1"); 24 binding.setVariable("str2", "value2"); 25 // binding.setVariable("newConcat", scrpt); 26 27 scrpt.setBinding(binding); 28 System.out.println(scrpt.evaluate("str1.concat(str2)")); 29 30 // 會拋異常 31 // Exception in thread "main" groovy.lang.MissingMethodException: No signature of method: Script1.customConcat() is applicable for argument types: (java.lang.String, java.lang.String) values: [value1, value2] 32 // at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:58) 33 // at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:81) 34 // at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52) 35 // at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:154) 36 // at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:174) 37 // at Script1.run(Script1.groovy:1) 38 System.out.println(scrpt.evaluate("customConcat(str1, str2)")); // 會拋異常 39 40 // 這種調用evaluate()執行腳本方法行不通是因沒有腳本里定義的方法沒有綁定,因此可以把script綁定給binding,然后執行的binding的引用方法: 41 System.out.println(scrpt.evaluate("newConcat.customConcat(str1, str2)")); // 解開第25行注釋就並且改為這樣子,就不會拋異常 42 } 43 }
2.2 通過Binding傳遞Java對象,groovy里面又可以調用該對象的方法
1 package groovy; 2 3 import groovy.lang.Binding; 4 import groovy.lang.GroovyShell; 5 import org.springframework.core.io.Resource; 6 import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 7 import org.springframework.core.io.support.ResourcePatternResolver; 8 9 import java.io.File; 10 import java.io.FileInputStream; 11 import java.io.IOException; 12 import java.io.InputStream; 13 14 public class GroovyShellEvaluate { 15 16 private String getContent(String classpathFile) { 17 StringBuilder content = new StringBuilder(); 18 ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); 19 Resource resource = resourcePatternResolver.getResource(classpathFile); 20 try { 21 File file = resource.getFile(); 22 InputStream is = new FileInputStream(file); 23 byte[] c = new byte[1024]; 24 int len = 0; 25 while ((len = is.read(c)) != -1) { 26 String buffer = new String(c, 0, len); 27 content.append(buffer); 28 } 29 } catch (IOException e) { 30 e.printStackTrace(); 31 } 32 return content.toString(); 33 } 34 35 private File getFile(String classpathFile) { 36 File file = null; 37 ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); 38 Resource resource = resourcePatternResolver.getResource(classpathFile); 39 try { 40 file = resource.getFile(); 41 } catch (IOException e) { 42 e.printStackTrace(); 43 } 44 return file; 45 } 46 47 public static void main(String[] args) throws IOException { 48 Binding binding = new Binding(); 49 Table table = new Table(); 50 binding.setProperty("tool", "car"); 51 binding.setProperty("t", table); // 傳遞java對象,groovy里面可以調用該對象的方法 52 GroovyShellEvaluate shellEvaluate = new GroovyShellEvaluate(); 53 File file = shellEvaluate.getFile("classpath:randomNumberGenerator.groovy"); 54 GroovyShell shell = new GroovyShell(binding); 55 shell.evaluate(file); 56 57 System.out.println("***************content**************"); 58 System.out.println(shellEvaluate.getContent("classpath:randomNumberGenerator.groovy")); 59 } 60 61 }
1 package groovy; 2 3 public class Table { 4 5 public String generate() { 6 return "[inTable] generate something"; 7 } 8 }
// randomNumberGenerator.groovy,腳本放在src/main/resources目錄下 def a = "hello" println "${a}, i want to use ${tool}" def res = t.generate() // 調用對象t的generate()方法 println res class rand { }
結果:
1 hello, i want to use car
2 [inTable] generate something 3 ***************content************** 4 def a = "hello" 5 println "${a}, i want to use ${tool}" 6 7 def res = t.generate() 8 println res 9 10 class rand { 11 12 }
3. MOP(MetaObject Protocol)& metaClass & 運行時元編程
3.1 運行時元編程
Groovy 提供兩類元編程,分別為:運行時元編程與編譯時元編程。第一種允許在運行時改變類模型,而第二種發生在編譯時。此處重點講解運行時元編程。(https://groovys.readthedocs.io/zh/latest/GettingStarted/Runtime-and-compile-time-metaprogramming.html#id1)
https://blog.csdn.net/Dream_Weave/article/details/106150352
在運行時元編程中,我們在運行時攔截,注入甚至合成類和接口的方法。為了深入理解 Groovy MOP , 我們需要理解 Groovy 的對象及方法的處理方式。Groovy 中有三類對象:POJO,POGO 和 Groovy 攔截器。Groovy 中對於以上對象均能使用元編程,但使用方式會有差別。
POJO
- 正規的 Java 對象,其類可以用Java或任何其他 JVM 語言編寫。POGO
- Groovy 對象,其類使用 Groovy 編寫。繼承於java.lang.Object
並且實現 groovy.lang.GroovyObject 接口。Groovy Interceptor
- Groovy 對象,實現groovy.lang.GroovyInterceptable
接口,具有方法攔截能力,我們將在GroovyInterceptable
章節詳細講解。
如下例子參考:https://segmentfault.com/a/1190000021090823
在Groovy中任何對象都實現GroovyObject接口,所以MyMetaTest 也默認實現了GroovyObject接口。如果調用MyMetaTest 中定義了的方法,如:hello,就會直接調用。如果調用MyMetaTest 中未定義方法,如:hello2,如果覆蓋了invokeMethod就會執行invokeMethod方法,否則拋出MissingMethodException異常。
1 // MyMetaTest.groovy 2 class MyMetaTest { 3 def hello() { 4 return 'invoked hello directly' 5 } 6 7 @Override 8 Object invokeMethod(String name, Object args) { 9 return "invokeMethod: name:${name}, args:${args}" 10 } 11 } 12 13 MyMetaTest test = new MyMetaTest() 14 println test.hello() // invoked hello directly 15 16 println test.hello2('kerwin') // invokeMethod: name:hello2, args:[kerwin]
讓MyMetaTest 實現GroovyInterceptable接口(Interceptable —— 可攔截),該接口是一個標記接口,沒有任何方法需要實現。從這個接口的描述可知:實現該接口的類,類中的方法被調用時都會默認使用invokeMethod方法,不管該方法是否已經定義。如果要直接調用已定義的方法,需要使用.&操作符。
1 class MyMetaTest implements GroovyInterceptable{ 2 def hello() { 3 return 'invoked hello directly' 4 } 5 6 @Override 7 Object invokeMethod(String name, Object args) { 8 return "invokeMethod: name:${name}, args:${args}" 9 } 10 } 11 12 MyMetaTest test = new MyMetaTest() 13 14 println test.hello() // invokeMethod: name:hello, args:[] 15 16 println test.hello2('kerwin') // invokeMethod: name:hello2, args:[kerwin] 17 18 println test.&hello() // invoked hello directly
3.2 什么是metaClass
理解:https://juejin.cn/post/6844904032968900621
在Groovy語言中,每個對象都有一個名稱為metaClass的MetaClass類的對象。 此metaClass對象負責保存與該對象有關的所有信息。 每當您對該對象執行任何操作時,Groovy的調度機制都會通過該元類對象(metaClass)路由調用。 因此,如果要更改任何對象/類的行為,則必須更改附加到該類/對象的MetaClass對象,並且它將在運行時更改該類/對象的行為。
deletegate 代理的是誰
大白話來說,誰調用就代理誰(實例對象)。
1 // metaClass1.groovy 2 Integer.metaClass.isEven = { -> 3 delegate%2 == 0 4 } 5 6 int a = 10 7 int b = 11 8 9 println "a.isEven(): ${a.isEven()}" 10 println "b.isEven(): ${b.isEven()}"
結果:groovy metaClass1.groovy
a.isEven(): true
b.isEven(): false
1 // metaClass2.groovy 2 Integer.metaClass.isEven = { -> 3 num%2 == 0 4 } 5 6 int a = 10 7 int b = 11 8 9 println "a.isEven(): ${a.isEven()}" 10 println "b.isEven(): ${b.isEven()}"
結果:groovy metaClass2.groovy
Caught: groovy.lang.MissingPropertyException: No such property: num for class: metaClass2
groovy.lang.MissingPropertyException: No such property: num for class: metaClass2
at metaClass2$_run_closure1.doCall(metaClass2.groovy:3)
at metaClass2.run(metaClass2.groovy:9)
1 // metaClassStatic.groovy 2 Integer.metaClass.static.isEven = { num -> 3 num%2 == 0 4 } 5 6 int a = 10 7 int b = 11 8 9 println Integer.isEven(1) 10 println "b.isEven(2): ${b.isEven(2)}"
結果:groovy metaClassStatic.groovy
false
b.isEven(2): true