- 簡(jiǎn)介
- Groovy語(yǔ)法特性(相比于Java)
- Groovy與Java項(xiàng)目集成使用
- Groovy實(shí)現(xiàn)相關(guān)原理
- Spring對(duì)Groovy以及動(dòng)態(tài)語(yǔ)言的支持
- Groovy運(yùn)行沙盒
簡(jiǎn)介
Groovy是構(gòu)建在JVM上的一個(gè)輕量級(jí)卻強(qiáng)大的動(dòng)態(tài)語(yǔ)言, 它結(jié)合了Python、Ruby和Smalltalk的許多強(qiáng)大的特性.
Groovy就是用Java寫(xiě)的 , Groovy語(yǔ)法與Java語(yǔ)法類似, Groovy 代碼能夠與 Java 代碼很好地結(jié)合,也能用于擴(kuò)展現(xiàn)有代碼, 相對(duì)于Java, 它在編寫(xiě)代碼的靈活性上有非常明顯的提升,Groovy 可以使用其他 Java 語(yǔ)言編寫(xiě)的庫(kù).
使用
- Groovy Console
- 安裝IDEA groovy插件
應(yīng)用
ElasticSearch, Jenkins 都支持執(zhí)行Groovy腳本
項(xiàng)目構(gòu)建工具Gradle就是Groovy實(shí)現(xiàn)的
Groovy語(yǔ)法特性(相比于Java)
不需要分號(hào)
return關(guān)鍵字可省略, 方法的最后一句表達(dá)式可作為返回值返回 (視具體情況使用, 避免降低可讀性)類的默認(rèn)作用域是
public, 不需要getter/setter方法def關(guān)鍵字定義的變量類型都是Object, 任何變量, 方法都能用def定義/聲明 , 在 Groovy 中 “一切都是對(duì)象 "-
導(dǎo)航操作符 ( ?. )可幫助實(shí)現(xiàn)對(duì)象引用不為空時(shí)方法才會(huì)被調(diào)用
// java if (object != null) { object.getFieldA(); } // groovy object?.getFieldA() -
命令鏈, Groovy 可以使你省略頂級(jí)語(yǔ)句方法調(diào)用中參數(shù)外面的括號(hào)?!懊铈湣惫δ軇t將這種特性繼續(xù)擴(kuò)展,它可以將不需要括號(hào)的方法調(diào)用串接成鏈,既不需要參數(shù)周圍的括號(hào),鏈接的調(diào)用之間也不需要點(diǎn)號(hào)
def methodA(String name) { println("A: " + name) return this } def methodB(String name) { println("B: " + name) return this } def methodC() { println("C") return this } def methodD(String name) { println("D: " + name) return this } methodA("xiaoming") methodB("zhangsan") methodC() methodD("lisi") // 不帶參數(shù)的鏈中需要用括號(hào) methodA "xiaoming" methodB "zhangsan" methodC() methodD "lisi"
-
閉包. 閉包是一個(gè)短的匿名代碼塊。每個(gè)閉包會(huì)被編譯成繼承g(shù)roovy.lang.Closure類的類,這個(gè)類有一個(gè)叫call方法,通過(guò)該方法可以傳遞參數(shù)并調(diào)用這個(gè)閉包.
def hello = {println "Hello World"} hello.call() // 包含形式參數(shù) def hi = { person1, person2 -> println "hi " + person1 + ", "+ person2 } hi.call("xiaoming", "xiaoli") // 隱式單個(gè)參數(shù), 'it'是Groovy中的關(guān)鍵字 def hh = { println("haha, " + it) } hh.call("zhangsan")
-
with語(yǔ)法, (閉包實(shí)現(xiàn))
// Java public class JavaDeamo { public static void main(String[] args) { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.MONTH, Calendar.DECEMBER); calendar.set(Calendar.DATE, 4); calendar.set(Calendar.YEAR, 2018); Date time = calendar.getTime(); System.out.println(time); } } // Groovy Calendar calendar = Calendar.getInstance() calendar.with { // it 指 calendar 這個(gè)引用 it.set(Calendar.MONTH, Calendar.DECEMBER) // 可以省略it, 使用命令鏈 set Calendar.DATE, 4 set Calendar.YEAR, 2018 // calendar.getTime() println(getTime()) // 省略get, 對(duì)于get開(kāi)頭的方法名并且 println(time) }
-
數(shù)據(jù)結(jié)構(gòu)的原生語(yǔ)法, 寫(xiě)法更便捷
def list = [11, 12, 13, 14] // 列表, 默認(rèn)是ArrayList def list = ['Angular', 'Groovy', 'Java'] as List // 字符串列表 // 同list.add(8) list << 8 [1, 2, [3, 4], 5] // 嵌套列表 ['Groovy', 21, 2.11] // 異構(gòu)的對(duì)象引用列表 [] // 一個(gè)空列表 def set = ["22", "11", "22"] as Set // LinkedHashSet, as運(yùn)算符轉(zhuǎn)換類型 def map = ['TopicName': 'Lists', 'TopicName': 'Maps'] // map, LinkedHashMap [:] // 空map // 循環(huán) map.each { print it.key }
- Groovy Truth
所有類型都能轉(zhuǎn)成布爾值,比如null, void 對(duì)象, 等同于 0 或空的值,都會(huì)解析為false,其他則相當(dāng)于true
-
groovy支持
DSL(Domain Specific Languages領(lǐng)域特定語(yǔ)言), DSL旨在簡(jiǎn)化以Groovy編寫(xiě)的代碼,使得它對(duì)于普通用戶變得容易理解借助命令鏈編寫(xiě)DSL
// groovy代碼 show = { println it } square_root = { Math.sqrt(it) } def please(action) { [the: { what -> [of: { n -> action(what(n)) }] }] } // DSL 語(yǔ)言: please show the square_root of 100 (請(qǐng)顯示100的平方根) // 調(diào)用, 等同于:please(show).the(square_root).of(100) please show the square_root of 100 // ==> 10.0
- Java 的
==實(shí)際相當(dāng)于 Groovy 的is()方法,而 Groovy 的==則是一個(gè)更巧妙的equals()。 在Groovy中要想比較對(duì)象的引用,不能用==,而應(yīng)該用a.is(b)- http://www.groovy-lang.org/syntax.html
- Differences with Java: http://www.groovy-lang.org/differences.html
Groovy與Java項(xiàng)目集成使用
項(xiàng)目中引入groovy依賴
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>x.y.z</version>
</dependency>
常見(jiàn)的集成機(jī)制:
GroovyShell
GroovyClassLoader
GroovyScriptEngine
JSR 223 javax.script API
GroovyShell
GroovyShell允許在Java類中(甚至Groovy類)求任意Groovy表達(dá)式的值。您可使用Binding對(duì)象輸入?yún)?shù)給表達(dá)式,并最終通過(guò)GroovyShell返回Groovy表達(dá)式的計(jì)算結(jié)果
解析為腳本(groovy.lang.Script)運(yùn)行
GroovyShell groovyShell = new GroovyShell();
groovyShell.evaluate("println \"hello world\"");
GroovyClassLoader
用 Groovy 的 GroovyClassLoader ,動(dòng)態(tài)地加載一個(gè)腳本并執(zhí)行它的行為。GroovyClassLoader是一個(gè)定制的類裝載器,負(fù)責(zé)解釋加載Java類中用到的Groovy類。
GroovyClassLoader loader = new GroovyClassLoader();
Class groovyClass = loader.parseClass(new File(groovyFileName)); // 也可以解析字符串
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
groovyObject.invokeMethod("run", "helloworld");
GroovyScriptEngine
groovy.util.GroovyScriptEngine 類為 GroovyClassLoader 其上再增添一個(gè)能夠處理腳本依賴及重新加載的功能層, GroovyScriptEngine可以從指定的位置(文件系統(tǒng),URL,數(shù)據(jù)庫(kù),等等)加載Groovy腳本
你可以使用一個(gè)CLASSPATH集合(url或者路徑名稱)初始化GroovyScriptEngine,之后便可以讓它根據(jù)要求去執(zhí)行這些路徑中的Groovy腳本了.GroovyScriptEngine同樣可以跟蹤相互依賴的腳本,如果其中一個(gè)被依賴的腳本發(fā)生變更,則整個(gè)腳本樹(shù)都會(huì)被重新編譯和加載。
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(file.getAbsolutePath());
groovyScriptEngine.run("hello.groovy", new Binding())
JSR-223
JSR-223 是 Java 中標(biāo)準(zhǔn)的腳本框架調(diào)用 API。從 Java 6 開(kāi)始引入進(jìn)來(lái),主要目用來(lái)提供一種常用框架,以便從 Java 中調(diào)用多種語(yǔ)言
ScriptEngine groovyEngine = new ScriptEngineManager().getEngineByName("groovy");
// 編譯成類
groovyEngine.compile(script)
// 直接執(zhí)行
groovyEngine.eval(script)
Groovy實(shí)現(xiàn)相關(guān)原理
groovy負(fù)責(zé)詞法、語(yǔ)法解析groovy文件,然后用ASM生成普通的java字節(jié)碼文件,供jvm使用。
Groovy代碼文件與class文件的對(duì)應(yīng)關(guān)系
作為基于JVM的語(yǔ)言,Groovy可以非常容易的和Java進(jìn)行互操作,但也需要編譯成class文件后才能運(yùn)行,所以了解Groovy代碼文件和class文件的對(duì)應(yīng)關(guān)系,有助于更好地理解Groovy的運(yùn)行方式和結(jié)構(gòu)。
對(duì)于沒(méi)有任何類定義
如果Groovy腳本文件里只有執(zhí)行代碼,沒(méi)有定義任何類(class),則編譯器會(huì)生成一個(gè)Script的子類,類名和腳本文件的文件名一樣,而腳本的代碼會(huì)被包含在一個(gè)名為run的方法中,同時(shí)還會(huì)生成一個(gè)main方法,作為整個(gè)腳本的入口。
對(duì)于僅有一個(gè)類
如果Groovy腳本文件里僅含有一個(gè)類,而這個(gè)類的名字又和腳本文件的名字一致,這種情況下就和Java是一樣的,即生成與所定義的類一致的class文件, Groovy類都會(huì)實(shí)現(xiàn)groovy.lang.GroovyObject接口。
對(duì)于多個(gè)類
如果Groovy腳本文件含有一個(gè)或多個(gè)類,groovy編譯器會(huì)很樂(lè)意地為每個(gè)類生成一個(gè)對(duì)應(yīng)的class文件。如果想直接執(zhí)行這個(gè)腳本,則腳本里的第一個(gè)類必須有一個(gè)static的main方法。
對(duì)于有定義類的腳本
如果Groovy腳本文件有執(zhí)行代碼, 并且有定義類, 那么所定義的類會(huì)生成對(duì)應(yīng)的class文件, 同時(shí), 腳本本身也會(huì)被編譯成一個(gè)Script的子類,類名和腳本文件的文件名一樣
Spring對(duì)Groovy以及動(dòng)態(tài)語(yǔ)言的支持
Spring 從2.0開(kāi)始支持將動(dòng)態(tài)語(yǔ)言集成到基于 Spring 的應(yīng)用程序中。Spring 開(kāi)箱即用地支持 Groovy、JRuby 和 BeanShell。以 Groovy、JRuby 或任何受支持的語(yǔ)言編寫(xiě)的應(yīng)用程序部分可以無(wú)縫地集成到 Spring 應(yīng)用程序中。應(yīng)用程序其他部分的代碼不需要知道或關(guān)心單個(gè) Spring bean 的實(shí)現(xiàn)語(yǔ)言。
動(dòng)態(tài)語(yǔ)言支持將 Spring 從一個(gè)以 Java 為中心的應(yīng)用程序框架改變成一個(gè)以 JVM 為中心的應(yīng)用程序框架
Spring 通過(guò) ScriptFactory 和 ScriptSource 接口支持動(dòng)態(tài)語(yǔ)言集成。ScriptFactory 接口定義用于創(chuàng)建和配置腳本 Spring bean 的機(jī)制。理論上,所有在 JVM 上運(yùn)行語(yǔ)言都受支持,因此可以選擇特定的語(yǔ)言來(lái)創(chuàng)建自己的實(shí)現(xiàn)。ScriptSource 定義 Spring 如何訪問(wèn)實(shí)際的腳本源代碼;例如,通過(guò)文件系統(tǒng), URL, 數(shù)據(jù)庫(kù)。
在使用基于 Groovy 的 bean 時(shí),則有幾種選擇:
將 Groovy 類編譯成普通的 Java 類文件
在一個(gè) .groovy 文件中定義 Groovy 類或腳本
在 Spring 配置文件中以內(nèi)聯(lián)方式編寫(xiě) Groovy 腳本
- 配置編譯的 Groovy 類, 和Java一樣的用法, 定義groovy class, 使用
<bean/>創(chuàng)建bean
class Test {
def printDate() {
println(new Date());
}
}
<bean id="test" class="com.qj.study.groovytest.spring.Test" />
ClassPathXmlApplicationContext context = newClassPathXmlApplicationContext("applicationContext.xml");
Test bean = (Test) context.getBean("test");
bean.printDate();
-
配置來(lái)自 Groovy 腳本的 bean
<bean/><lang:groovy>
-
<bean/>示例:
<bean id="demo" class="org.springframework.scripting.groovy.GroovyScriptFactory">
<constructor-arg value="classpath:script/ScriptBean.groovy"/>
</bean>
<bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/>
-
<lang:groovy/>示例:
<lang:groovy id="demo" script-source="classpath:script/ScriptBean.groovy">
</lang:groovy>
<bean class="org.springframework.scripting.support.ScriptFactoryPostProcessor"/>
實(shí)現(xiàn)過(guò)程:
Groovy 語(yǔ)言集成通過(guò) ScriptFactory 的 GroovyScriptFactory 實(shí)現(xiàn)得到支持
當(dāng) Spring 裝載應(yīng)用程序上下文時(shí),它首先創(chuàng)建工廠 bean(這里是GroovyScriptFactory 類型的bean)。然后,執(zhí)行 ScriptFactoryPostProcessor bean中的postProcessBeforeInstantiation方法,用實(shí)際的腳本對(duì)象替換所有的工廠 bean。
ScriptFactoryPostProcessor:
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
// 只處理ScriptFactory類型的bean
if (!ScriptFactory.class.isAssignableFrom(beanClass)) {
return null;
}
// ...
// 加載并解析groovy代碼, 在scriptBeanFactory中注冊(cè)BeanDefinition
prepareScriptBeans(bd, scriptFactoryBeanName, scriptedObjectBeanName);
// ...
}
// prepareScriptBeans調(diào)用createScriptedObjectBeanDefinition
protected BeanDefinition createScriptedObjectBeanDefinition(BeanDefinition bd, String scriptFactoryBeanName,
ScriptSource scriptSource, @Nullable Class<?>[] interfaces) {
GenericBeanDefinition objectBd = new GenericBeanDefinition(bd);
objectBd.setFactoryBeanName(scriptFactoryBeanName);
// 指定工廠方法, ScriptFactory.getScriptedObject, 創(chuàng)建腳本的Java對(duì)象
objectBd.setFactoryMethodName("getScriptedObject");
objectBd.getConstructorArgumentValues().clear();
objectBd.getConstructorArgumentValues().addIndexedArgumentValue(0, scriptSource);
objectBd.getConstructorArgumentValues().addIndexedArgumentValue(1, interfaces);
return objectBd;
}
創(chuàng)建bean的時(shí)候, SimpleInstantiationStrategy.instantiate
// 調(diào)用工廠方法創(chuàng)建beanInstance
Object result = factoryMethod.invoke(factoryBean, args);
if (result == null) {
result = new NullBean();
}
GroovyScriptFactory.getScriptedObject
// 通過(guò)groovyClassLoader 加載并解析類
this.scriptClass = getGroovyClassLoader().parseClass( scriptSource.getScriptAsString(), scriptSource.suggestedClassName());
if (Script.class.isAssignableFrom(this.scriptClass)) {
// 如果是groovy 腳本, 那么運(yùn)行腳本, 將結(jié)果的類作為Bean的類型
Object result = executeScript(scriptSource, this.scriptClass);
this.scriptResultClass = (result != null ? result.getClass() : null);
return result;
}
else {
// 不是腳本, 直接返回類
this.scriptResultClass = this.scriptClass;
}
protected Object executeScript(ScriptSource scriptSource, Class<?> scriptClass) throws ScriptCompilationException {
try {
GroovyObject goo = (GroovyObject) ReflectionUtils.accessibleConstructor(scriptClass).newInstance();
// GroovyObjectCustomizer 是一個(gè)回調(diào),Spring 在創(chuàng)建一個(gè) Groovy bean 之后會(huì)調(diào)用它。可以對(duì)一個(gè) Groovy bean 應(yīng)用附加的邏輯,或者執(zhí)行元編程
if (this.groovyObjectCustomizer != null) {
this.groovyObjectCustomizer.customize(goo);
}
if (goo instanceof Script) {
// A Groovy script, probably creating an instance: let's execute it.
return ((Script) goo).run();
}
else {
// An instance of the scripted class: let's return it as-is.
return goo;
}
}
catch (NoSuchMethodException ex) {
// ...
}
最終在ScriptFactoryPostProcessor中, scriptBeanFactory保存了所有通過(guò)腳本創(chuàng)建的bean, scriptSourceCache緩存了所有的腳本信息
final DefaultListableBeanFactory scriptBeanFactory = new DefaultListableBeanFactory();
/** Map from bean name String to ScriptSource object */
private final Map<String, ScriptSource> scriptSourceCache = new HashMap<String, ScriptSource>();
- refresh參數(shù)
<lang:groovy id="refresh" refresh-check-delay="1000"
script-source="classpath:script/RefreshBean.groovy">
</lang:groovy>
創(chuàng)建的是JdkDynamicAopProxy代理對(duì)象, 在每一次調(diào)用這個(gè)代理對(duì)象的方法的時(shí)候, 都回去校驗(yàn)被代理對(duì)象是否需要刷新, 通過(guò)比對(duì)腳本文件的最后更新時(shí)間和設(shè)定的更新時(shí)間間隔, 如果需要刷新則重新加載這個(gè)groovy文件, 并編譯, 然后創(chuàng)建一個(gè)新的bean并注冊(cè)進(jìn)行替換
3.內(nèi)聯(lián)方式配置
inline script標(biāo)簽, 從配置中讀取源代碼
<lang:groovy id="inline">
<lang:inline-script>
<![CDATA[
class InlineClass {
// xxxxx ...
}
]]>
</lang:inline-script>
</lang:groovy>
綜上, 擴(kuò)展一下, 脫離xml配置, 可以從數(shù)據(jù)庫(kù)中定時(shí)加載groovy代碼, 構(gòu)建/更新/刪除BeanDefinition
Groovy運(yùn)行沙盒
沙盒原理也叫沙箱,英文sandbox。在計(jì)算機(jī)領(lǐng)域指一種虛擬技術(shù),且多用于計(jì)算機(jī)安全技術(shù)。安全軟件可以先讓它在沙盒中運(yùn)行,如果含有惡意行為,則禁止程序的進(jìn)一步運(yùn)行,而這不會(huì)對(duì)系統(tǒng)造成任何危害。
舉個(gè)例子:
docker容器可以理解為在沙盒中運(yùn)行的進(jìn)程。這個(gè)沙盒包含了該進(jìn)程運(yùn)行所必須的資源。不同的容器之間相互隔離。CGroup實(shí)現(xiàn)資源控制, Namespace實(shí)現(xiàn)訪問(wèn)隔離, rootfs實(shí)現(xiàn)文件系統(tǒng)隔離。
對(duì)于嵌入Groovy的Java系統(tǒng), 如果暴露接口, 可能存在的隱患有
通過(guò)Java的
Runtime.getRuntime().exec()方法執(zhí)行shell, 操作服務(wù)器.....執(zhí)行
System.exit(0)dump 內(nèi)存中的Class, 修改內(nèi)存中的緩存數(shù)據(jù)
Groovy提供了編譯自定義器(Compilation customizers), 無(wú)論你使用 groovyc 還是采用 GroovyShell 來(lái)編譯類,要想執(zhí)行腳本,實(shí)際上都會(huì)使用到編譯器配置(compiler configuration)信息。這種配置信息保存了源編碼或類路徑這樣的信息,而且還用于執(zhí)行更多的操作,比如默認(rèn)添加導(dǎo)入,顯式使用 AST(語(yǔ)法樹(shù)) 轉(zhuǎn)換,或者禁止全局 AST 轉(zhuǎn)換, 編譯自定義器的目標(biāo)在于使這些常見(jiàn)任務(wù)易于實(shí)現(xiàn)。CompilerConfiguration 類就是切入點(diǎn)。
groovy sandbox的實(shí)現(xiàn) -> https://github.com/jenkinsci/groovy-sandbox
實(shí)現(xiàn)過(guò)程:
groovy-sandbox實(shí)現(xiàn)了一個(gè)SandboxTransformer, 擴(kuò)展自CompilationCustomizer, 在Groovy代碼編譯時(shí)進(jìn)行轉(zhuǎn)換. 腳本轉(zhuǎn)換后, 讓腳本執(zhí)行的每一步都會(huì)被攔截, 調(diào)用Checker進(jìn)行檢查
可攔截所有內(nèi)容,包括
- 方法調(diào)用(實(shí)例方法和靜態(tài)方法)
- 對(duì)象分配(即除了“this(...)”和“super(...)”之外的構(gòu)造函數(shù)調(diào)用
- 屬性訪問(wèn)(例如,z = foo.bar,z = foo?!癰ar”)和賦值(例如,foo.bar = z,foo?!癰ar”= z)
- 數(shù)組訪問(wèn)和賦值
當(dāng)然, 執(zhí)行性能也會(huì)受到一些的影響
示例: Jenkins Pipline支持在Groovy沙盒中執(zhí)行Groovy腳本

其他: