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)
}
}
方法調用流程就先總結這些,歡迎評論指正。
參考資料: