Groovy學(xué)習(xí)目錄-傳送門
元編程(Metaprogramming)->百度百科
Groovy語言支持兩種類型的元編程:運行時元編程和編譯時元編程溉愁。 第一個允許在運行時改變類模型和程序的行為熔恢,而第二個只在編譯時發(fā)生。 兩者都有利弊录淡,我們將在本節(jié)詳細介紹诱鞠。
運行時元編程
使用運行時元編程,我們可以在運行時截取,注入愈犹,合成類和接口的方法。 為了更深入地理解Groovy MOP闻丑,我們需要了解Groovy對象和Groovy的方法處理漩怎。 在Groovy中,我們使用了三種對象:POJO嗦嗡,POGO和Groovy Interceptors勋锤。 Groovy允許對所有類型的對象進行元編程,但是是以不同的方式進行的侥祭。
- POJO - 一個常規(guī)的Java對象叁执,它的類是用Java或任何其他運行在JVM上的編程語言編寫的。
- POGO - 一個Groovy對象卑硫,它的類是用Groovy編寫的。 它擴展了
java.lang.Object
并在默認情況下實現(xiàn)了groovy.lang.GroovyObject
接口蚕断。 - Groovy Interceptor - 一個Groovy對象欢伏,它實現(xiàn)了
groovy.lang.GroovyInterceptable
接口并具有方法攔截功能,我們將在GroovyInterceptable
部分討論亿乳。
對于每個方法調(diào)用硝拧,Groovy檢查對象是POJO還是POGO径筏。 對于POJO,Groovy從groovy.lang.MetaClassRegistry獲取它的MetaClass障陶,并將方法調(diào)用委托給它滋恬。 對于POGO,Groovy需要更多步驟抱究,如下圖所示:
GroovyObject接口
groovy.lang.GroovyObject是Groovy中的主要接口恢氯,就像Object
類是Java中的主要接口一樣。GroovyObject
在groovy.lang.GroovyObjectSupport類中有一個默認實現(xiàn)鼓寺,它負責(zé)將調(diào)用傳遞給groovy.lang.MetaClass對象勋拟。 GroovyObject
源代碼如下:
package groovy.lang;
public interface GroovyObject {
Object invokeMethod(String name, Object args);
Object getProperty(String propertyName);
void setProperty(String propertyName, Object newValue);
MetaClass getMetaClass();
void setMetaClass(MetaClass metaClass);
}
invokeMethod
根據(jù)運行時元編程中的模式,當您調(diào)用的方法不存在于Groovy對象上時妈候,將調(diào)用此方法敢靡。 下面是一個重寫invokeMethod()
方法的簡單示例:
class SomeGroovyClass {
def invokeMethod(String name, Object args) {
return "called invokeMethod $name $args"
}
def test() {
return 'method exists'
}
}
def someGroovyClass = new SomeGroovyClass()
assert someGroovyClass.test() == 'method exists'
assert someGroovyClass.someMethod() == 'called invokeMethod someMethod []'
get/setProperty
對屬性的每個讀取訪問都可以通過覆蓋當前對象的getProperty()
方法來攔截。 這里有一個簡單的例子:
class SomeGroovyClass {
def property1 = 'ha'
def field2 = 'ho'
def field4 = 'hu'
def getField1() {
return 'getHa'
}
def getProperty(String name) {
if (name != 'field3')
return metaClass.getProperty(this, name) //將請求轉(zhuǎn)發(fā)到getter以獲取除field3之外的所有屬性苦银。
else
return 'field3'
}
}
def someGroovyClass = new SomeGroovyClass()
assert someGroovyClass.field1 == 'getHa'
assert someGroovyClass.field2 == 'ho'
assert someGroovyClass.field3 == 'field3'
assert someGroovyClass.field4 == 'hu'
您可以通過覆蓋setProperty()
方法來攔截對屬性的寫訪問權(quán)限:
class POGO {
String property
void setProperty(String name, Object value) {
this.@"$name" = 'overridden'
}
}
def pogo = new POGO()
pogo.property = 'a'
assert pogo.property == 'overridden'
get/setMetaClass
您可以訪問對象的metaClass
或設(shè)置您自己的MetaClass
實現(xiàn)以更改默認攔截機制啸胧。 例如,您可以編寫自己的MetaClass
接口實現(xiàn)幔虏,并將其分配給對象纺念,從而更改攔截機制:
// getMetaclass
someObject.metaClass
// setMetaClass
someObject.metaClass = new OwnMetaClassImplementation()
您可以在GroovyInterceptable主題中找到其他示例。
get/setAttribute
此功能與MetaClass
實現(xiàn)相關(guān)所计。 在默認實現(xiàn)中柠辞,您可以訪問字段而不調(diào)用它們的getter和setter。 下面的示例演示了這種方法:
class SomeGroovyClass {
def field1 = 'ha'
def field2 = 'ho'
def getField1() {
return 'getHa'
}
}
def someGroovyClass = new SomeGroovyClass()
assert someGroovyClass.metaClass.getAttribute(someGroovyClass, 'field1') == 'ha'
assert someGroovyClass.metaClass.getAttribute(someGroovyClass, 'field2') == 'ho'
class POGO {
private String field
String property1
void setProperty1(String property1) {
this.property1 = "setProperty1"
}
}
def pogo = new POGO()
pogo.metaClass.setAttribute(pogo, 'field', 'ha')
pogo.metaClass.setAttribute(pogo, 'property1', 'ho')
assert pogo.field == 'ha'
assert pogo.property1 == 'ho'
methodMissing
Groovy支持methodMissing
的概念主胧。 此方法與invokeMethod
的不同之處在于叭首,它僅在失敗的方法分派的情況下被調(diào)用,當沒有找到給定名稱和/或給定參數(shù)的方法時:
class Foo {
def methodMissing(String name, def args) {
return "this is me"
}
}
assert new Foo().someUnknownMethod(42l) == 'this is me'
通常當使用methodMissing
時踪栋,可以緩存結(jié)果焙格,以便下次調(diào)用相同的方法。
例如夷都,考慮GORM中的動態(tài)查找器眷唉。 這些是根據(jù)methodMissing
來實現(xiàn)的。 代碼類似這樣:
class GORM {
def dynamicMethods = [...] // an array of dynamic methods that use regex
def methodMissing(String name, args) {
def method = dynamicMethods.find { it.match(name) }
if(method) {
GORM.metaClass."$name" = { Object[] varArgs ->
method.invoke(delegate, name, varArgs)
}
return method.invoke(delegate,name, args)
}
else throw new MissingMethodException(name, delegate, args)
}
}
注意如果我們找到一個方法來調(diào)用囤官,隨后就會使用ExpandoMetaClass動態(tài)地注冊一個新的方法冬阳。 這是為了下次更有效率的調(diào)用相同的方法。 這種使用methodMissing
的方法不像invokeMethod
一樣党饮,這種方式在第二次調(diào)用時并不產(chǎn)生昂貴開銷肝陪。
propertyMissing
Groovy支持propertyMissing
的概念,用于攔截那些失敗的屬性解析嘗試刑顺。 在getter方法的情況下氯窍,propertyMissing
接受一個包含屬性名稱的String
參數(shù):
class Foo {
def propertyMissing(String name) { name }
}
assert new Foo().boo == 'boo'
僅當Groovy運行時找不到給定屬性的getter方法時才調(diào)用propertyMissing(String)
方法饲常。
對于setter方法,可以添加另一個propertyMissing
定義狼讨,它接受一個附加的value參數(shù):
class Foo {
def storage = [:]
def propertyMissing(String name, value) { storage[name] = value }
def propertyMissing(String name) { storage[name] }
}
def f = new Foo()
f.foo = "bar"
assert f.foo == "bar"
與methodMissing
一樣贝淤,最佳做法是在運行時動態(tài)注冊新屬性,以提高總體查找性能政供。
methodMissing
和propertyMissing
方法處理靜態(tài)方法和屬性可以通過ExpandoMetaClass添加播聪。
GroovyInterceptable
groovy.lang.GroovyInterceptable接口是用于通知Groovy運行時的擴展GroovyObject
的標記接口,所有方法都應(yīng)通過Groovy運行時的方法分派器機制攔截鲫骗。
package groovy.lang;
public interface GroovyInterceptable extends GroovyObject {
}
當Groovy對象實現(xiàn)GroovyInterceptable
接口時犬耻,對任何方法的調(diào)用都會調(diào)用invokeMethod()
方法。 下面你可以看到一個這種類型的對象的簡單例子:
class Interception implements GroovyInterceptable {
def definedMethod() { }
def invokeMethod(String name, Object args) {
'invokedMethod'
}
}
下一段代碼是一個測試执泰,顯示對現(xiàn)有和不存在的方法的調(diào)用都將返回相同的值枕磁。
class InterceptableTest extends GroovyTestCase {
void testCheckInterception() {
def interception = new Interception()
assert interception.definedMethod() == 'invokedMethod'
assert interception.someMethod() == 'invokedMethod'
}
}
我們不能使用默認groovy方法,如println
术吝,因為這些方法被注入到所有g(shù)roovy對象中计济,所以它們也會被攔截。
如果我們要攔截所有方法調(diào)用排苍,但不想實現(xiàn)GroovyInterceptable
接口沦寂,我們可以在對象的MetaClass
上實現(xiàn)invokeMethod()
。 此方法適用于POGO和POJO淘衙,如此示例所示:
class InterceptionThroughMetaClassTest extends GroovyTestCase {
void testPOJOMetaClassInterception() {
String invoking = 'ha'
invoking.metaClass.invokeMethod = { String name, Object args ->
'invoked'
}
assert invoking.length() == 'invoked'
assert invoking.someMethod() == 'invoked'
}
void testPOGOMetaClassInterception() {
Entity entity = new Entity('Hello')
entity.metaClass.invokeMethod = { String name, Object args ->
'invoked'
}
assert entity.build(new Object()) == 'invoked'
assert entity.someMethod() == 'invoked'
}
}
有關(guān)MetaClass
的其他信息传藏,請參閱MetaClasses部分。
Categories分類
有些情況下彤守,如果想為一個無法控制的類添加額外的方法毯侦,Categories就派上用場了。 為了啟用此功能具垫,Groovy實現(xiàn)了一個從Objective-C借用的功能侈离,稱為Categories。
Categories使用所謂的Category 類實現(xiàn)筝蚕。Category 類是特殊的類卦碾,因為它需要滿足用于定義擴展方法的某些預(yù)定義規(guī)則。
系統(tǒng)中包括幾個Categories起宽,用于向類添加功能洲胖,以使這些類在Groovy環(huán)境中更易于使用:
默認情況下不啟用Category 類。 要使用Category 類中定義的方法坯沪,需要使用由GDK提供并可從每個Groovy對象實例中獲取的use
方法:
use(TimeCategory) {
println 1.minute.from.now //TimeCategory給Integer添加了方法
println 10.hours.ago
def someDate = new Date() //TimeCategory將方法添加到Date
println someDate - 3.months
}
use
方法將Category 類作為其第一個參數(shù)绿映,將閉包代碼塊作為第二個參數(shù)。 在Closure
內(nèi)可以訪問category的方法屏箍。 從上面的例子可以看出绘梦,甚至像java.lang.Integer
或java.util.Date
這樣的JDK中的類也可以用用戶定義的方法來豐富功能。
一個category不需要直接暴露給用戶代碼赴魁,如下:
class JPACategory{
//讓我們在不修改JPA EntityManager的前提下卸奉,增強JPA EntityManager的功能
static void persistAll(EntityManager em , Object[] entities) { //添加一個接口用于保存所有entities
entities?.each { em.persist(it) }
}
}
def transactionContext = {
EntityManager em, Closure c ->
def tx = em.transaction
try {
tx.begin()
use(JPACategory) {
c()
}
tx.commit()
} catch (e) {
tx.rollback()
} finally {
//清理你的資源
}
}
// 用戶代碼,他們總是忘記關(guān)閉異常中的資源颖御,有些甚至忘記提交榄棵,讓我們不要依賴他們。
EntityManager em; //可能注射
transactionContext (em) {
em.persistAll(obj1, obj2, obj3)
// let's do some logics here to make the example sensible
//讓我們在這里做一些邏輯潘拱,使示例理智
em.persistAll(obj2, obj4, obj6)
}
當我們看一下groovy.time.TimeCategory
類疹鳄,我們看到擴展方法都被聲明為static
方法。 事實上芦岂,這是類方法必須滿足的要求之一瘪弓,它的方法被成功地添加到use
代碼塊中的類中:
public class TimeCategory {
public static Date plus(final Date date, final BaseDuration duration) {
return duration.plus(date);
}
public static Date minus(final Date date, final BaseDuration duration) {
final Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.YEAR, -duration.getYears());
cal.add(Calendar.MONTH, -duration.getMonths());
cal.add(Calendar.DAY_OF_YEAR, -duration.getDays());
cal.add(Calendar.HOUR_OF_DAY, -duration.getHours());
cal.add(Calendar.MINUTE, -duration.getMinutes());
cal.add(Calendar.SECOND, -duration.getSeconds());
cal.add(Calendar.MILLISECOND, -duration.getMillis());
return cal.getTime();
}
// ...
}
另一個要求是靜態(tài)方法的第一個參數(shù)必須定義該方法一旦被激活時附加的類型。 其他參數(shù)是方法將作為參數(shù)的正常參數(shù)禽最。
由于參數(shù)和靜態(tài)方法約定腺怯,category方法定義可能比正常的方法定義更不直觀。 作為替代Groovy帶有一個@Category
注解川无,在編譯時將加了注解的類轉(zhuǎn)換為category 類呛占。
class Distance {
def number
String toString() { "${number}m" }
}
@Category(Number)
class NumberCategory {
Distance getMeters() {
new Distance(number: this)
}
}
use (NumberCategory) {
assert 42.meters.toString() == '42m'
}
應(yīng)用@Category
注解具有能夠使用沒有目標類型作為第一個參數(shù)的實例方法的優(yōu)點。 目標類型class作為注釋的參數(shù)提供懦趋。
在compile-time metaprogramming section中晾虑,有一個關(guān)于@Category
的完整章節(jié)
MateClass
待定
自定義MateClass
待定
- 委托MateClass
待定 - Magic package (Maksym Stavytskyi)
待定
每個metaclass實例
待定
ExpandoMetaClass
Groovy帶有一個特殊的MetaClass
,它就是ExpandoMetaClass
仅叫。 它是特別的帜篇,它允許通過使用一個整潔的閉包語法動態(tài)添加或更改方法,構(gòu)造函數(shù)惑芭,屬性坠狡,甚至靜態(tài)方法。
應(yīng)用這些修改在模擬(mock)或樁(stub)情況下特別有用遂跟,如測試指南中所示逃沿。
每個java.lang.Class
由Groovy提供,并有一個特殊的metaClass
屬性幻锁,它將提供對ExpandoMetaClass
實例的引用凯亮。 然后,此實例可用于添加方法或更改已有現(xiàn)有方法的行為哄尔。
默認情況下ExpandoMetaClass
不執(zhí)行繼承假消。 要啟用它,您必須在應(yīng)用程序啟動之前調(diào)用ExpandoMetaClass#enableGlobally()
岭接,例如在main方法或servlet引導(dǎo)中富拗。
以下部分詳細介紹了ExpandoMetaClass
如何在各種情況下使用臼予。
方法
一旦通過調(diào)用metaClass
屬性訪問ExpandoMetaClass
,可以使用左移<<
或=
運算符來添加方法啃沪。
注意粘拾,左移位運算符用于附加一個新方法。 如果類或接口聲明了具有相同名稱和參數(shù)類型的公共方法创千,包括繼承自父類和父接口但不包括在運行時添加到metaClass
的那些方法缰雇,那么將拋出異常。 如果要替換由類或接口聲明的方法追驴,可以使用=
運算符械哟。
運算符通過一個Closure代碼塊實例,將功能應(yīng)用于metaClass的不存在的屬性上殿雪。
class Book {
String title
}
Book.metaClass.titleInUpperCase << {-> title.toUpperCase() }
def b = new Book(title:"The Stand")
assert "THE STAND" == b.titleInUpperCase()
上面的例子顯示了如何通過訪問metaClass
屬性并使用<<
或=
運算符來分配一個Closure
代碼塊來將新方法添加到類中暇咆。 Closure
參數(shù)被解釋為方法參數(shù)。 無參數(shù)方法可以使用{-> ...}
語法添加丙曙。
屬性
ExpandoMetaClass
支持兩種機制來添加或覆蓋屬性糯崎。
首先,它支持通過向metaClass
的屬性賦值來聲明一個可變屬性:
class Book {
String title
}
Book.metaClass.author = "Stephen King"
def b = new Book()
assert "Stephen King" == b.author
另一種方法是通過使用添加實例方法的標準機制來添加getter和/或setter方法河泳。
class Book {
String title
}
Book.metaClass.getAuthor << {-> "Stephen King" }
def b = new Book()
assert "Stephen King" == b.author
在上面的源代碼示例中沃呢,屬性由閉包指定,并且是只讀屬性拆挥。 可以添加一個等效的setter方法薄霜,但是該屬性值需要被存儲以備將來使用。 這可以如下面的示例所示完成纸兔。
class Book {
String title
}
def properties = Collections.synchronizedMap([:])
Book.metaClass.setAuthor = { String value ->
properties[System.identityHashCode(delegate) + "author"] = value
}
Book.metaClass.getAuthor = {->
properties[System.identityHashCode(delegate) + "author"]
}
這不是唯一的方式惰瓜。 例如,在servlet容器中汉矿,一種方式可能是將值作為請求屬性存儲在當前執(zhí)行的請求中(如在Grails中的某些情況下所做的那樣)崎坊。
構(gòu)造函數(shù)
可以通過使用特殊的構(gòu)造函數(shù)屬性來添加構(gòu)造函數(shù)。 可以使用<<
或=
運算符來分配Closure
代碼塊洲拇。 當代碼在運行時執(zhí)行時奈揍,Closure
參數(shù)將成為構(gòu)造函數(shù)參數(shù)。
class Book {
String title
}
Book.metaClass.constructor << { String title -> new Book(title:title) }
def book = new Book('Groovy in Action - 2nd Edition')
assert book.title == 'Groovy in Action - 2nd Edition'
當添加構(gòu)造函數(shù)時要小心赋续,因為它很容易陷入堆棧溢出問題男翰。
靜態(tài)方法
可以使用與實例方法相同的技術(shù)添加靜態(tài)方法,并在方法名稱之前添加靜態(tài)限定符纽乱。
class Book {
String title
}
Book.metaClass.static.create << { String title -> new Book(title:title) }
def b = Book.create("The Stand")
借用方法
使用ExpandoMetaClass
蛾绎,可以使用Groovy的方法指針語法從其他類中借用方法。
class Person {
String name
}
class MortgageLender {
def borrowMoney() {
"buy house"
}
}
def lender = new MortgageLender()
Person.metaClass.buyHouse = lender.&borrowMoney
def p = new Person()
assert "buy house" == p.buyHouse()
動態(tài)方法名稱
由于Groovy允許使用Strings作為屬性名,這反過來允許您在運行時動態(tài)創(chuàng)建方法和屬性名租冠。 要創(chuàng)建具有動態(tài)名稱的方法鹏倘,只需使用引用屬性名稱的語言特性作為字符串。
class Person {
String name = "Fred"
}
def methodName = "Bob"
Person.metaClass."changeNameTo${methodName}" = {-> delegate.name = "Bob" }
def p = new Person()
assert "Fred" == p.name
p.changeNameToBob()
assert "Bob" == p.name
相同的概念可以應(yīng)用于靜態(tài)方法和屬性顽爹。
動態(tài)方法名稱的一個應(yīng)用程序可以在Grails Web應(yīng)用程序框架中找到第股。 “動態(tài)編解碼器”的概念通過使用動態(tài)方法名稱來實現(xiàn)。
HTMLCodec
類
class HTMLCodec {
static encode = { theTarget ->
HtmlUtils.htmlEscape(theTarget.toString())
}
static decode = { theTarget ->
HtmlUtils.htmlUnescape(theTarget.toString())
}
}
上面的示例顯示了編解碼器實現(xiàn)话原。 Grails有各種編解碼器實現(xiàn),每個在一個類中定義诲锹。 在運行時繁仁,應(yīng)用程序類路徑中將有多個編解碼器類。 在應(yīng)用程序啟動時归园,框架向某些元類添加encodeXXX和decodeXXX方法黄虱,其中XXX是編解碼器類名稱的第一部分(例如,encodeHTML)庸诱。 這種機制在下面顯示的一些Groovy偽代碼中:
def codecs = classes.findAll { it.name.endsWith('Codec') }
codecs.each { codec ->
Object.metaClass."encodeAs${codec.name-'Codec'}" = { codec.newInstance().encode(delegate) }
Object.metaClass."decodeFrom${codec.name-'Codec'}" = { codec.newInstance().decode(delegate) }
}
def html = '<html><body>hello</body></html>'
assert '<html><body>hello</body></html>' == html.encodeAsHTML()
運行時發(fā)現(xiàn)
在運行時捻浦,知道在執(zhí)行該方法時存在什么其他方法或?qū)傩酝ǔJ怯杏玫摹? ExpandoMetaClass
在撰寫本文時提供了以下方法:
getMetaMethod
hasMetaMethod
getMetaProperty
hasMetaProperty
你為什么不能使用反射? 因為Groovy是不同的桥爽,它有方法是“真正的”方法朱灿,同時方法只在運行時可用。 這些有時(但不總是)表示為MetaMethods钠四。 MetaMethod告訴你在運行時可用的方法盗扒,因此你的代碼可以適應(yīng)。
當覆蓋invokeMethod
缀去,getProperty
和/或setProperty
時侣灶,這是特別有用的。
GroovyObject 方法
ExpandoMetaClass
的另一個特點是它允許重寫方法invokeMethod
缕碎,getProperty
和setProperty
褥影,所有這些都可以在groovy.lang.GroovyObject
類中找到。
以下示例顯示如何覆蓋invokeMethod
:
class Stuff {
def invokeMe() { "foo" }
}
Stuff.metaClass.invokeMethod = { String name, args ->
def metaMethod = Stuff.metaClass.getMetaMethod(name, args)
def result
if(metaMethod) result = metaMethod.invoke(delegate,args)
else {
result = "bar"
}
result
}
def stf = new Stuff()
assert "foo" == stf.invokeMe()
assert "bar" == stf.doStuff()
Closure
代碼的第一步是查找給定名稱和參數(shù)的MetaMethod
咏雌。 如果方法可以找到一切都很好凡怎,它被委托。 如果不是赊抖,則返回一個虛擬值栅贴。
擴展模塊
擴展現(xiàn)有類
擴展模塊允許您向現(xiàn)有類添加新方法,包括預(yù)編譯的類熏迹,如JDK中的類檐薯。 這些新方法與通過元類或使用類定義的方法不同,在全局范圍內(nèi)可用。 例如坛缕,當您寫:
標準擴展方法
def file = new File(...)
def contents = file.getText('utf-8')
File
類中不存在getText
方法墓猎。 但是,Groovy可以使你調(diào)用它赚楚,因為它在一個特殊的類ResourceGroovyMethods
中定義:
ResourceGroovyMethods.java
public static String getText(File file, String charset) throws IOException {
return IOGroovyMethods.getText(newReader(file, charset));
}
您可能會注意到毙沾,擴展方法是在輔助類(其中定義了各種擴展方法)中使用靜態(tài)方法定義的。 getText
方法的第一個參數(shù)對應(yīng)于接收者宠页,而附加參數(shù)對應(yīng)于擴展方法的參數(shù)左胞。 所以這里,我們在File
類上定義一個名為 getText 的方法(因為第一個參數(shù)是File
類型)举户,它接受一個參數(shù)作為參數(shù)(編碼String
)烤宙。
創(chuàng)建擴展模塊的過程很簡單:
- 編寫一個類似上面的擴展類
- 寫一個模塊描述符文件
然后你必須讓擴展模塊對Groovy可見,這就像在classpath上有擴展模塊類和描述符一樣簡單俭嘁。 這意味著您可以選擇:
- 直接在類路徑上提供類和模塊描述符
- 或?qū)⒛臄U展模塊捆綁到一個jar中以便重用
擴展模塊可以向類添加兩種方法:
- 實例方法(要在類的實例上調(diào)用)
- 靜態(tài)方法(要在類本身上調(diào)用)
實例方法
要向現(xiàn)有類添加實例方法躺枕,您需要創(chuàng)建擴展類。 例如供填,假設(shè)您要在Integer
中添加一個maxRetries
方法拐云,該方法接受一個閉包并執(zhí)行它最多n次,直到?jīng)]有拋出異常近她。 要做到這一點叉瘩,你只需要寫如下:
MaxRetriesExtension.groovy
class MaxRetriesExtension { // 擴展類
static void maxRetries(Integer self, Closure code) { //靜態(tài)方法的第一個參數(shù)對應(yīng)于消息的接收者,也就是說擴展實例
int retries = 0
Throwable e
while (retries<self) {
try {
code.call()
break
} catch (Throwable err) {
e = err
retries++
}
}
if (retries==0 && e) {
throw e
}
}
}
然后粘捎,在聲明你的擴展類之后房揭,你可以這樣調(diào)用:
int i=0
5.maxRetries {
i++
}
assert i == 1
i=0
try {
5.maxRetries {
throw new RuntimeException("oops")
}
} catch (RuntimeException e) {
assert i == 5
}
靜態(tài)方法
還可以向類添加靜態(tài)方法。 在這種情況下晌端,靜態(tài)方法需要在其自己的文件中定義捅暴。 靜態(tài)和實例擴展方法不能出現(xiàn)在同一個類中。
StaticStringExtension.groovy
class StaticStringExtension { //靜態(tài)擴展類
static String greeting(String self) { //靜態(tài)方法的第一個參數(shù)對應(yīng)于正在擴展并且未使用的類
'Hello, world!'
}
}
在這種情況下咧纠,您可以直接在String
類上調(diào)用它:
assert String.greeting() == 'Hello, world!'
模塊描述符
要使Groovy能夠加載您的擴展方法蓬痒,必須聲明擴展幫助器類。 您必須在META-INF/services
目錄中創(chuàng)建一個名為org.codehaus.groovy.runtime.ExtensionModule
的文件:
org.codehaus.groovy.runtime.ExtensionModule
moduleName=Test module for specifications
moduleVersion=1.0-test
extensionClasses=support.MaxRetriesExtension
staticExtensionClasses=support.StaticStringExtension
模塊描述符需要4個鍵:
- moduleName:模塊的名稱
- moduleVersion:您的模塊的版本漆羔。 請注意梧奢,版本號僅用于檢查您不加載同一模塊在兩個不同的版本。
- extensionClasses:實例方法的擴展輔助類列表演痒。 您可以提供幾個類亲轨,假定它們是逗號分隔的。
- staticExtensionClasses:靜態(tài)方法的擴展輔助類列表鸟顺。 您可以提供幾個類惦蚊,假定它們是逗號分隔的器虾。
請注意,模塊不需要定義靜態(tài)輔助函數(shù)和實例輔助函數(shù)蹦锋,并且可以向單個模塊添加多個類兆沙。 您還可以在單個模塊中擴展不同的類,而不會有問題莉掂。 甚至可能在單個擴展類中使用不同的類葛圃,但建議按擴展類將擴展方法分組。
擴展模塊和類路徑
值得注意的是憎妙,你不能使用一個在使用它的代碼同時編譯的擴展库正。 這意味著要使用擴展,它必須在類路徑上可用厘唾,作為編譯類褥符,在使用它的代碼被編譯之前。 通常阅嘶,這意味著您不能將測試類與擴展類本身放在同一個源單元中。 因為一般來說载迄,測試源與正常源分離并在構(gòu)建的另一步驟中執(zhí)行讯柔,這不是問題。
與類型檢查的兼容性
與Category不同护昧,擴展模塊與類型檢查兼容:如果它們在類路徑中找到魂迄,那么類型檢查器知道擴展方法,并且不會在調(diào)用它們時抱怨惋耙。 它也與靜態(tài)編譯兼容捣炬。