Groovy MOP-1

Groovy作为动态语言自然少不了一个重要东西就是元编程(metaprogramming
,在groovy 我们根据元对象协议/MOPmetaobject protocol)进行动态编程。

MOP:

Groovy每个对象都有一个对应的metaclass。而我们动态编程就是在metaclass进行对于原始对象/Class进行方法或者属性修改或增加。

注意:class也是一个对象,也就是一个实例会关联两个metaclass,一个是类的,一个是实例的。优先级是实例metaclass才到类的metaclass

  • metaclass在哪?
    对于原始的Java创建的类,那么在GroovyMetaClassRegistry会保存一个对应某个java类所对应的metaclass。而对于groovy创建的类,由于groovy默认实现GroovyObject接口(这个接口的实现由groovy负责)。GroovyObject接口中存在方法返回一个metaclass的方法。
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);
}

其实MOP还有一套方法/属性调用机制,我们先看看如何给一个类动态的扩展一个属性或者方法。

静态方法/属性注入:

给一个String类扩展一个静态方法sayHello

String.metaClass.'static'.sayHello = {
    println("hello world")
}

String.'sayHello'()
String.sayHello()

暂时未找到静态属性注入,还望知道的指点

构造函数注入:

给一个String类扩展构造方法,传入一个Integer然后返回实例

String.metaClass.constructor = { Integer p ->
    new String("hello world" + p)
}

println new String(1)

实例属性/方法注入

String.metaClass.myName = "XiaoMing"

println "xx".myName

String.metaClass.sayHello = {
    println('hello world')
}

"xx".sayHello()

一次性注入多个属性/方法

String.metaClass {
    myName = "XiaoMing"
    cry = {
        println("cry")
    }
    //静态注入
    'static' {
        sayHello = {
            println("hello")
        }
        run = {
            println("I'm runing")
        }
    }
}
String.run()
"String".cry()
println "String".myName

ExpandoMetaClass方式

       //1 创建一个ExpandoMetaClass
        def eMetaClass = new ExpandoMetaClass(String)
        eMetaClass.say = "hello world"
        //2 完成注入后调用init初始化。注意初始化后不许再扩展
        eMetaClass.initialize()
        //3 注入
        String.metaClass = eMetaClass

        new MyTgroovy().test()
        println "sd".say

以上扩展对于后续的所有的String类都生效,如果我们只想对某个具体的实例进行扩展,而不影响的其他所有String类实例。我们只需要取出实例的metaclass进行扩展即可(注意实例和类各自有一个metaclass

def strOne = new String("hello")
//以下方法获取实例的类metaclass的代理类,注意是代理类,
//实际类通过strOne.@metaClass获取
//但是你直接用strOne.@metaClass进行扩展会出错,后续讲解为什么
strOne.metaClass.cry = {
    println("invoke cry")
}
//同过
strOne.cry()

def strTwo = new String("hello")
//报错
//strTwo.cry()

<<扩展方法

当你只想对对象没有的方法进行扩展,如果对象存在某个方法那么抛出错误,可以用如下方法.如果对象父级metaclass别存在此方法那么不覆盖。

class Person {
    def test1() {
        System.out.println("invoke test1")
        return "test1"
    }
}


Person.metaClass.test1 <<{
    println("say hello")
}

输出:

Exception in thread "main" groovy.lang.GroovyRuntimeException: Cannot add new method [test1] for 
..

覆盖父级别的方法情况:

import com.MyGroovy

class Person {
    def test1() {
        System.out.println("invoke test1")
        return "test1"
    }
}


Person.metaClass.say <<{
    println("say hello")
}


def expando = new ExpandoMetaClass(Person.class)
expando.say << {
    println("hello world")
}
expando.initialize()

def person = new Person()
person.metaClass = expando
//这里输出say hello
person.say()

输出:

say hello

方法借用

以下说groovy官方demo:

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()

如何调用非metaclass扩展的函数/属性?

方法一适用于方法和属性:

class Person {

    def myName = "XiaoMing"

    def run2() {
        println("invoke origin run2")
    }


}

Person.metaClass.run2 = { ->
    println("invoke metaclass run2")
}
Person.metaClass.myName = "ZhangSan"
Person.metaClass = null

def person = new Person()

person.run2()
println person.myName


输出:

invoke origin run2
XiaoMing

方法二适用于属性,方法还是会调用metaclass:

class Person {

    def myName = "XiaoMing"

    def run2() {
        println("invoke origin run2")
    }


}

Person.metaClass.run2 = { ->
    println("invoke metaclass run2")
}
Person.metaClass.myName = "ZhangSan"

def person = new Person()

person.run2() //invoke metaclass run2
println person.@myName //输出XiaoMing
println person.myName //ZhangSan

metaclass扩展java类内部调用失效

假设我们有一个java创建的类Student.java

//Student.java
package com;

public class Student {

    public void testOne() {
        System.out.println("invoke origin testOne");
        //这里还是会输出invoke origin testTwo
        testTwo();
    }

    private void testTwo() {
        System.out.println("invoke origin testTwo");
    }
}

然后我们在groovy环境下运行

import com.Student

Student.metaClass.testTwo={
    println("invoke metaclass test2")
}

new Student().testOne()

失效的原因是Groovy生成的类内部存在方法动态调用的代码,而java没有。

metaclass获取实例不一致问题

类存在一个metaclass实例,而类的实例自己也有一个metaclass,当没有进行动态对实例的metaclass扩展时,类的metaclass和实例的metaclass是同一个对象。

class Person {

    def test(outMetaclass) {
        println(metaClass == outMetaclass)
    }
}

def person = new Person()
person.test(person.metaClass)
person.test(person.@metaClass)

println(Person.metaClass == person.@metaClass)

输出:

false
true
true

第一个输出false的原因,是因为groovy在生成类的时候会在代码中生成许多代理函数和属性,而在外部直接person.metaClass获取的metaclass是通过动态代理从metaclass池得到的一个,而内部的得到metaclass是其内部的一个属性,默认的情况它指向类的metaclass

并且注意一个问题metaclass实现类并不都具有动态扩展能力。

class Person {


}

def person = new Person()

println("动态代理metaclass:" + person.metaClass)
println("内部属性metaclass:" + person.@metaClass)

//报错
[email protected]="XiaoMing"


输出

动态代理metaclass:org.codehaus.groovy.runtime.HandleMetaClass@34c4973[groovy.lang.MetaClassImpl@34c4973[class Person]]
内部属性metaclass:groovy.lang.MetaClassImpl@34c4973[class Person]
Exception in thread "main" groovy.lang.MissingPropertyException: No such property: myName 
.....
.....

内部属性得到的metaclass实现类MetaClassImpl不具备扩展能力。
如果上面的代码换成动态代理得到的metaclass即可正常正确运行

class Person {


}

def person = new Person()

println("动态代理metaclass:" + person.metaClass)
println("内部属性metaclass:" + person.@metaClass)

//通过
person.metaClass.myName = "XiaoMing"

输出:

  • 注意点: 当groovy某个类的实例通过实例的metaclass 动态注入一个方法/属性后,某个类的实例的属性metaclas将改变为ExpandoMetaClass的一个实例,这个实例将会属性metaclass也支持动态注入方法/属性
class Person {


}

def person = new Person()

println("动态代理metaclass:" + person.metaClass)
println("内部属性metaclass:" + person.@metaClass)

//报错 属性metaclass是MetaClassImpl 所以不支持动态注入
//[email protected] = "XiaoMing"

//通过
person.metaClass.myName = "XiaoMing"

println("------------动态注入方法后-----------")
println("动态代理metaclass:" + person.metaClass)
println("内部属性metaclass:" + person.@metaClass)

//因为属性metaClass变为ExpandoMetaClass,所以这里不会报错
[email protected] = "XiaoMing"

  • 注意点: 当groovy某个类通过类的metaclass动态注入一个方法/属性后,那么这个类的实例的属性metaclas将改变为ExpandoMetaClass的一个实例,这个实例将会属性metaclass也支持动态注入方法/属性
class Person {


}

println("Person这个类没有动态注入前:" + Person.metaClass)
Person.metaClass.myName = ""
println("Person这个类有动态注后:" + Person.metaClass)

def instance = new Person()
println("instance 属性的metaclass:" + instance.@metaClass)
println("instance 动态代理metaclass:" + instance.metaClass)
//不报错 因为这里属性metaclass在Person metaclass改动的时候变化了
[email protected] = "你好"

metaclass 相关方法

  • respondsTo
    respondsTo传入参数:
    List respondsTo(Object obj, String name, Object[] argTypes);

第一个参数:查找哪个对象
第二个参数:方法名
第三个参数: 参数类数组

如果存在方法那么返回在list中,如果没有返回空list

class Person {

    def name = "XiaoMing"

    def run2(String name, int age) {

    }

    def test() {
        println("invoke test  ")
        println metaClass.respondsTo(this, "run2", String,int)
    }
}


def person = new Person()

person.test()

输出:

invoke test  
[public java.lang.Object Person.run2(java.lang.String,int)]

对于metaclass进行扩展的话的那么默认的返回metaclass的函数,而不会返回原本的,respondsTo仅会返回一个方法。

class Person {

    def name = "XiaoMing"

    def run2(String name, int age) {

    }

    def test() {
        println("invoke test  ")
       println  metaClass.respondsTo(this, "run2", String,int)
    }
}

Person.metaClass.run2={String name,int age->
    println("Person class metacalss invoke run2")
}

def person = new Person()


person.test()
invoke test  
[org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod@76f2b07d[name: run2 params: [class java.lang.String, int] returns: class java.lang.Object owner: class Person]]
  • hasProperty,getMetaProperty是否存在某个某个属性
class Person {

    def myName = "XiaoMing"

    def run2(String name, int age) {

    }

    def test(propertiesName) {
        println("invoke test  ")
        if (metaClass.hasProperty(this, propertiesName)) {
            println("存在属性")
        } else {
            println("不存在")

        }

    }
}


def person = new Person()


person.test("myName")
person.test("myName2sdd")

输出:

invoke test  
存在属性
invoke test  
不存在
  • invokeMethod
    调用指定的方法
class Person {

    def myName = "XiaoMing"

    def run2(String name, int age) {
        println("I am $name and $age  ")
    }

   
}


def person = new Person()

person.metaClass.invokeMethod(person, "run2","Xiaoming", 13)



getAttributesetAttribute

两个函数用于直接设置/获取字段,而不是通过get/set方法。

class Person {

    def myName = "XiaoMing"


    void setMyName(String name) {
        println("setMyName")
        this.myName = name
    }


}


def person = new Person()
person.myName="我阿尼"
println("开始调用setAttribute")
person.metaClass.setAttribute(person, "myName", "woaini")
println("调用setAttribute之后")



输出:

setMyName
开始调用setAttribute
调用setAttribute之后

Delegate注解

假设你存在Person类,需要实现一个run方法,而这个方法在Work中是存在的,那么你可以使用多个办法完成委托,但是这里使用了一种特别的方式。如下:

class Work {

    def run() {
        println("I'm Work and running")
    }
}

class Person {
    @Delegate
    Work work = new Work()

}

def person = new Person()
person.run()

你可能感兴趣的:(Groovy&Gradle)