Groovy學(xué)習(xí)之-運行時元編程

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需要更多步驟抱究,如下圖所示:

圖1. Groovy攔截機制

GroovyObject接口

groovy.lang.GroovyObject是Groovy中的主要接口恢氯,就像Object類是Java中的主要接口一樣。GroovyObjectgroovy.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)注冊新屬性,以提高總體查找性能政供。

methodMissingpropertyMissing方法處理靜態(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.Integerjava.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缕碎,getPropertysetProperty褥影,所有這些都可以在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)編譯兼容捣炬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市绽榛,隨后出現(xiàn)的幾起案子湿酸,更是在濱河造成了極大的恐慌,老刑警劉巖灭美,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件推溃,死亡現(xiàn)場離奇詭異,居然都是意外死亡届腐,警方通過查閱死者的電腦和手機铁坎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來犁苏,“玉大人硬萍,你說我怎么就攤上這事∥辏” “怎么了朴乖?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我寒砖,道長赐劣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任哩都,我火速辦了婚禮魁兼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘漠嵌。我一直安慰自己咐汞,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布儒鹿。 她就那樣靜靜地躺著化撕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪约炎。 梳的紋絲不亂的頭發(fā)上植阴,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音圾浅,去河邊找鬼掠手。 笑死,一個胖子當著我的面吹牛狸捕,可吹牛的內(nèi)容都是我干的喷鸽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼灸拍,長吁一口氣:“原來是場噩夢啊……” “哼做祝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鸡岗,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤混槐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后轩性,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體纵隔,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年炮姨,在試婚紗的時候發(fā)現(xiàn)自己被綠了捌刮。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡舒岸,死狀恐怖绅作,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蛾派,我是刑警寧澤俄认,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布个少,位于F島的核電站,受9級特大地震影響眯杏,放射性物質(zhì)發(fā)生泄漏夜焦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一岂贩、第九天 我趴在偏房一處隱蔽的房頂上張望茫经。 院中可真熱鬧,春花似錦萎津、人聲如沸卸伞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荤傲。三九已至,卻和暖如春颈渊,著一層夾襖步出監(jiān)牢的瞬間遂黍,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工俊嗽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留雾家,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓乌询,卻偏偏與公主長得像榜贴,于是被迫代替她去往敵國和親豌研。 傳聞我的和親對象是個殘疾皇子妹田,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)鹃共,斷路器鬼佣,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,822評論 6 342
  • 國家電網(wǎng)公司企業(yè)標準(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 10,983評論 6 13
  • Groovy 2.0新特性http://www.infoq.com/cn/articles/new-groovy-...
    葡萄喃喃囈語閱讀 1,958評論 0 2
  • 我們都是修仙的人,大家一起在一個修仙學(xué)院霜浴。但一開始剛進來的時候老人會欺負新人晶衷,我們就是新人。 我和晗一起被關(guān)在一個...
    愛夢的我閱讀 247評論 0 0