Groovy語法學習(七)MOP探索之方法調用流程

groovy是一門具有元對象協議(Meta Object Protocol)或稱 MOP的語言。在運行時向一個對象傳遞方法,或者消息時,這個協議使對象可以作出影響它自己的狀態或者行爲的特定選擇。簡單的說我們可以在運行時改變、增減類或者對象的方法、屬性等,讓其行爲在運行時進行改變。這個在java裏看起來四虎有些不可思議,但在groovy裏可以簡單的實現。
我們先看一張圖,然後通過例子來理解一下。
這裏寫圖片描述

主要關注這幾個方法。

  • methodMissing //方法丟失
  • propertyMissing //屬性丟失
  • invokeMethod //調用

    一、 正常情況這三個方法的調用關係。

class Person {

    def salary

    def develop() {
        println 'develop'
    }


    def methodMissing(String name, def args) {
        println "methodMissing $name,$args"
        return null
    }

    def propertyMissing(String name) {
        return null
    }



    @Override
    Object invokeMethod(String name, Object args) {
        System.out.println "invokeMethod $name,$args"
        return null
    }

}

new Person().develop()
new Person().develop1123()
new Person().salary1

結果:

develop
methodMissing develop1123,[]
propertyMissing salary1

在屬性丟失或者方法丟失的時候沒有調用invoke方法,如果把
- methodMissing
- propertyMissing
這兩個方法去掉
結果就變成了:

develop
invokeMethod develop1123,[]
Caught: groovy.lang.MissingPropertyException: No such property: salary1 for class: Person

結論:在方法丟失時,在沒有methodMissing方法會調用invoke方法,在屬性丟失沒有propertyMissing會直接拋出異常。

二、實現GroovyInterceptable接口這三個方法的調用關係。

class Person implements GroovyInterceptable {

    def salary

    def develop() {
        println 'develop'
    }

    //方法丟失
    def methodMissing(String name, def args) {
        println "methodMissing $name,$args"
        return null
    }
    //屬性丟失
    def propertyMissing(String name) {
        println 'propertyMissing'
        return null
    }


    @Override
    Object invokeMethod(String name, Object args) {
        System.out.println "invokeMethod $name,$args"
        return null
    }

}

new Person().develop()
new Person().develop1123()
new Person().salary1

結果:

invokeMethod develop,[]
invokeMethod develop1123,[]
invokeMethod println,[propertyMissing]

可以看到,所有的方法都被invokeMethod攔截了,注意在invokeMethod方法中,我使用了java的日誌輸出方式,這是因爲如果直接使用println就會拋出StackOverflowError的異常,很容易就能想到這是因爲方法循環調用的原因。如何繞過攔截,這時候就需要使用metClass了。我們把invokeMethod方法修改成這樣。

  @Override
    Object invokeMethod(String name, Object args) {
//        System.out.println "invokeMethod $name,$args"
        metaClass.invokeMethod(this,'println', "invokeMethod $name,$args")
        return null
    }

結果和之前完全一致,不會拋出異常,這樣使用就成功繞過了攔截。

三、與metaClass結合的調用關係

我們把Person類的metaClass的invokeMethod方法來修改一下

class Person implements GroovyInterceptable {

    def salary

    def develop() {
        println 'develop'
    }

    //方法丟失
    def methodMissing(String name, def args) {
        println "methodMissing $name,$args"
        return null
    }
    //屬性丟失
    def propertyMissing(String name) {
        println 'propertyMissing'
        return null
    }


    @Override
    Object invokeMethod(String name, Object args) {
//        System.out.println "invokeMethod $name,$args"
        metaClass.invokeMethod(this, 'println', "invokeMethod $name,$args")
        return null
    }

}

Person.metaClass.invokeMethod = {
    name, args ->
        System.out.println "metaclass invokeMethod $name,$args"

}

new Person().develop()
new Person().develop1123()
new Person().salary1

結果:

metaclass invokeMethod println,[invokeMethod develop,[]]
metaclass invokeMethod println,[invokeMethod develop1123,[]]
metaclass invokeMethod println,[invokeMethod println,[propertyMissing]]

metaClass的優先級更高了,直接把前面的攔截了。
我們可以利用metaClass來修改本身類的方法,在運行時修改類的行爲。

class Person implements GroovyInterceptable {

    def salary

    def develop() {
        println 'develop'
    }


}

def p =new Person()
Person.metaClass.develop={
    println 'static newDevelop'
}
p.metaClass.develop={
    println 'instance newDevelop'
}
p.develop()

def newPerson=new Person()
newPerson.develop()

p.metaClass.salary=100
println p.salary
println p.@salary

p.metaClass.newSalary=50
println p.newSalary
println p.@newSalary

結果:

instance newDevelop
static newDevelop
100
null
50
Caught: groovy.lang.MissingFieldException: No such field: newSalary for class: Person

結論:使用類的metaClass會影響之後新建對象的行爲,使用實例的metaClass只會改變當前對象的行爲。直接爲metaClass指定新的屬性,只是產生新的方法,不會產生新的屬性。

最後注意一點,如果要這樣使用,需要使用閉包的delegate來拿到對應的metaClass,比如這裏的delegate也就是p。

def p=new Person()
p.metaClass.invokeMethod = {
    name, args ->
        System.out.println "metaclass invokeMethod $name,$args"
        def method = delegate.metaClass.getMetaMethod(name, args)
        if (method) {
            method.invoke(delegate, args)
        }

}

方法調用流程就先總結這些,歡迎評論指正。

參考資料:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章